#!/usr/bin/env python3
# Copyright (c) 2026 PULSE Contributors. Proprietary. All rights reserved.
"""Reflex Arc — Real-Time Mistake Detection in Bridge.

Zero-cost regex detector that catches explicit mistake signals in agent
conversations and writes them INSTANTLY to reflex_patterns.json.
Live-searched by brain_query on every call — no consolidation wait.

Hook: called from pulse_bridge._flush_interaction() after auto_capture.
"""
from __future__ import annotations

import hashlib
import re
import time
from datetime import datetime, timezone

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

REFLEX_FILE = EVIDENCE_DIR / "Anti_Patterns" / "reflex_patterns.json"
_cooldowns: dict[str, float] = {}  # pattern_name -> last_write_timestamp
_COOLDOWN_SECONDS = 300             # 5 min per pattern category
_MAX_ENTRIES = 500                  # FIFO cap

# ---------------------------------------------------------------------------
# Pattern tiers (compiled once at import — zero per-call cost)
# ---------------------------------------------------------------------------

# Tier 1: Complete anti-pattern (problem + fix) -> confidence 0.90
_TIER1 = [
    ("bug_and_fix", re.compile(
        r"(?:the\s+)?bug\s+was\s+(.{10,120}?)[\.\,]\s*(?:the\s+)?fix\s+(?:was|is)\s+(.{10,120})",
        re.IGNORECASE)),
    ("broke_use_instead", re.compile(
        r"(.{10,120}?)\s+broke\s+because\s+(.{10,80}?)[\.\,]\s*(?:use|switch\s+to|replace\s+with)\s+(.{5,80})",
        re.IGNORECASE)),
    ("never_because", re.compile(
        r"never\s+(.{5,120}?)\s+because\s+(.{10,120})",
        re.IGNORECASE)),
    ("dont_use_use", re.compile(
        r"(?:don'?t|do\s+not)\s+use\s+(.{5,80}?)[\.\,;\-]+\s*(?:use|prefer|switch\s+to)\s+(.{5,80})",
        re.IGNORECASE)),
]

# Tier 2: Problem signal -> confidence 0.75
_TIER2 = [
    ("bug_was", re.compile(
        r"(?:the\s+)?bug\s+was\s+(.{10,200})", re.IGNORECASE)),
    ("root_cause", re.compile(
        r"root\s+cause(?:\s+(?:was|is))?[:\s]+(.{10,200})", re.IGNORECASE)),
    ("broke_because", re.compile(
        r"(.{10,80}?)\s+(?:broke|crashed|failed|dies)\s+because\s+(.{10,200})",
        re.IGNORECASE)),
    ("issue_was", re.compile(
        r"(?:the\s+)?(?:issue|problem)\s+(?:was|is)\s+(.{10,200})", re.IGNORECASE)),
    ("traceback", re.compile(
        r"((?:Traceback|(?:\w+Error|Exception):\s+).{10,200})")),
]

# Tier 3: Lesson/insight -> confidence 0.80
_TIER3 = [
    ("fix_was", re.compile(
        r"(?:the\s+)?fix\s+(?:was|is)\s+(.{10,200})", re.IGNORECASE)),
    ("always_do", re.compile(
        r"always\s+(.{10,120}?)\s+(?:because|when|before|after)\s+(.{5,120})",
        re.IGNORECASE)),
    ("switched_because", re.compile(
        r"(?:switched|replaced|changed)\s+(?:from\s+)?(.{5,60}?)\s+(?:to|with)\s+(.{5,60}?)\s+because\s+(.{10,120})",
        re.IGNORECASE)),
]

_ALL_TIERS = [
    (_TIER1, "problem_and_fix", 0.90),
    (_TIER2, "problem_signal", 0.75),
    (_TIER3, "lesson_signal", 0.80),
]


def detect_mistakes(text: str) -> list[dict]:
    """Run reflex patterns against text. Returns detected anti-patterns.

    Max 3 results (1 per tier). Returns [] for short text.
    """
    if not text or len(text) < 20:
        return []
    results = []
    for patterns, pattern_type, confidence in _ALL_TIERS:
        for name, regex in patterns:
            m = regex.search(text)
            if m:
                groups = [g.strip() for g in m.groups() if g]
                desc = " — ".join(groups) if groups else m.group(0).strip()
                results.append({
                    "description": desc[:300],
                    "pattern_type": pattern_type,
                    "pattern_name": name,
                    "confidence": confidence,
                })
                break  # one match per tier
    return results


def check_reflex(user_text: str, agent_text: str, source: str) -> int:
    """Detect mistakes and write to reflex_patterns.json. Returns count written."""
    combined = f"{user_text}\n{agent_text}" if agent_text else user_text
    detections = detect_mistakes(combined)
    if not detections:
        return 0
    now_t = time.time()
    now_iso = datetime.now(timezone.utc).isoformat()
    written = 0
    for det in detections:
        cat = det["pattern_name"]
        if (now_t - _cooldowns.get(cat, 0.0)) < _COOLDOWN_SECONDS:
            continue
        desc_hash = hashlib.md5(det["description"].lower().strip().encode()).hexdigest()

        def _updater(data, _det=det, _hash=desc_hash, _iso=now_iso, _src=source):
            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})
            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": _det["description"],
                "pattern_type": _det["pattern_type"],
                "pattern_name": _det["pattern_name"],
                "source_agent": _src.upper(),
                "confidence": _det["confidence"],
                "severity": "high" if _det["confidence"] >= 0.90 else "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:
            atomic_update_json(REFLEX_FILE, _updater,
                               default={"entries": [], "stats": {"total": 0}})
            _cooldowns[cat] = now_t
            written += 1
        except Exception:
            pass  # Never crash the bridge
    return written
