#!/usr/bin/env python3
# Copyright (c) 2026 PULSE Contributors. Proprietary. All rights reserved.
"""
Anti-Pattern Immune System — Brain V5 Phase 1

Replaces V1 cosine-similarity heuristics with LLM-based semantic detection.
Two modes:
  - Synchronous: check_against_anti_patterns() for direct callers needing immediate results
  - Async dispatch: dispatch_antipattern_check() posts [CHECK_ANTIPATTERN] tasks to the Swarm board
"""
from __future__ import annotations

import json
import logging
import sys
import time
from pathlib import Path

SCRIPT_DIR = Path(__file__).resolve().parent
ROOT_DIR = SCRIPT_DIR.parent.parent.parent
if str(ROOT_DIR) not in sys.path:
    sys.path.insert(0, str(ROOT_DIR))

from pulse.core.brain_utils import ANTI_PATTERNS_DIR, EVIDENCE_DIR, load_json

log = logging.getLogger("pulse.immune")

ACTIVE_ANTI_PATTERNS = ANTI_PATTERNS_DIR / "anti_patterns_active.json"

# ── LLM Prompts ──────────────────────────────────────────────────────────────

CHECK_PROMPT = """You are the PULSE Immune System. Check if the given text/code violates any known anti-patterns.

ANTI-PATTERNS:
{patterns}

TEXT TO CHECK:
{text}

OUTPUT strict JSON only (no markdown fences, no explanation):
{{"violations": [{{"anti_pattern": "which pattern was violated", "reason": "specific explanation", "severity": "low|medium|high"}}], "clean": true|false}}
If no violations found: {{"violations": [], "clean": true}}"""

CLUSTER_PROMPT = """You are the PULSE Immune System. Group these user preferences by semantic similarity.
Items that express the same underlying concern belong together.

ITEMS:
{items}

OUTPUT strict JSON only (no markdown fences):
{{"clusters": [[0, 3, 7], [1, 4], [2, 5, 6]]}}
Each inner list = 0-based indices of items that belong in the same group."""


# ── Helpers ───────────────────────────────────────────────────────────────────

def _load_anti_patterns_text() -> str:
    """Load anti-patterns as text for LLM context."""
    from pulse.core.brain_db import get_active_anti_patterns
    patterns = get_active_anti_patterns()
    if patterns:
        return "\n".join(
            f"- {p.get('description', '')} (severity: {p.get('severity', 'medium')})"
            for p in patterns if p.get("status") == "active"
        )
    return "No active anti-patterns found."


# ── Synchronous API (backward-compatible) ─────────────────────────────────────

def check_against_anti_patterns(text: str) -> list:
    """Semantically check text against active anti-patterns via LLM.

    Returns list of dicts with keys: anti_pattern, reason, severity.
    Falls back to empty list on LLM failure.
    """
    from pulse.core.llm_router import dispatch, extract_json

    patterns_text = _load_anti_patterns_text()
    if patterns_text == "No active anti-patterns found.":
        return []

    prompt = CHECK_PROMPT.format(patterns=patterns_text, text=text[:4000])
    try:
        raw = dispatch(prompt, task_type="fast")
        result = extract_json(raw)
        violations = result.get("violations", [])
        return [v for v in violations if isinstance(v, dict)]
    except Exception as e:
        log.warning("Immune check failed: %s", e)
        return []


def cluster_dont_wants(dont_wants: list) -> list:
    """Semantically cluster DON'T_WANTS via LLM.

    Returns list of clusters, where each cluster is a list of DON'T_WANT items.
    """
    if len(dont_wants) < 2:
        return [dont_wants] if dont_wants else []

    from pulse.core.llm_router import dispatch, extract_json

    items_text = "\n".join(
        f"[{i}] {dw.get('dont_want', '')}" for i, dw in enumerate(dont_wants)
    )
    prompt = CLUSTER_PROMPT.format(items=items_text[:6000])

    try:
        raw = dispatch(prompt, task_type="fast")
        result = extract_json(raw)
        index_clusters = result.get("clusters", [])

        clusters = []
        for idx_list in index_clusters:
            cluster = []
            for idx in idx_list:
                if isinstance(idx, int) and 0 <= idx < len(dont_wants):
                    cluster.append(dont_wants[idx])
            if cluster:
                clusters.append(cluster)
        return clusters if clusters else [[dw] for dw in dont_wants]
    except Exception as e:
        log.warning("Immune clustering failed: %s", e)
        return [[dw] for dw in dont_wants]


# ── Async Swarm Dispatch ──────────────────────────────────────────────────────

def _make_immune_task(task_id: str, title: str, desc: str, memory_data: dict) -> dict:
    """Create a task dict for the immune system."""
    return {
        "task_id": task_id, "title": title, "description": desc,
        "domain": "immune_system", "complexity": "low", "recommended_tier": "fast",
        "required_capabilities": [], "depends_on": [], "status": "open",
        "claimed_by": None, "claimed_at": None, "files_touched": [],
        "outcome": None, "memory_data": memory_data,
    }


def dispatch_antipattern_check(changed_files: list, source: str = "immune_system") -> int:
    """Post [CHECK_ANTIPATTERN] tasks to the Swarm board for async execution.

    Args:
        changed_files: List of Path objects or strings of changed files
        source: Identifier for what triggered the check

    Returns:
        Number of tasks dispatched
    """
    from pulse.tasks.pulse_dispatcher import TaskDispatcher

    patterns_text = _load_anti_patterns_text()
    if patterns_text == "No active anti-patterns found.":
        return 0

    tasks = []
    ts_prefix = int(time.time())
    for fp in changed_files:
        fp = Path(fp)
        if not fp.exists() or fp.stat().st_size > 50000:
            continue
        try:
            content = fp.read_text(encoding="utf-8", errors="replace")
        except Exception:
            continue

        try:
            rel_path = str(fp.relative_to(ROOT_DIR))
        except ValueError:
            rel_path = str(fp)

        n = len(tasks) + 1
        tasks.append(_make_immune_task(
            f"immune_{ts_prefix}_{n:03d}",
            f"[CHECK_ANTIPATTERN] {rel_path}",
            f"Semantic anti-pattern check for {rel_path}",
            {
                "type": "CHECK_ANTIPATTERN",
                "file_path": rel_path,
                "file_content": content[:15000],
                "anti_patterns": patterns_text[:8000],
                "source": source,
            },
        ))

    if not tasks:
        return 0

    dispatcher = TaskDispatcher(verbose=False)
    dispatcher.dispatch(
        prompt=f"[IMMUNE] Check {len(tasks)} files for anti-pattern violations",
        tasks=tasks, founder_override=True,
    )
    log.info("Immune system dispatched %d CHECK_ANTIPATTERN tasks", len(tasks))
    return len(tasks)


# ── CLI ───────────────────────────────────────────────────────────────────────

if __name__ == "__main__":
    logging.basicConfig(level=logging.INFO, format="%(asctime)s [IMMUNE] %(message)s")

    if len(sys.argv) > 1 and sys.argv[1] == "--check":
        if len(sys.argv) < 3:
            print("Usage: python anti_pattern_similarity.py --check <text_or_file>")
            raise SystemExit(1)
        target = sys.argv[2]
        p = Path(target)
        text = p.read_text(encoding="utf-8") if p.exists() else target
        matches = check_against_anti_patterns(text)
        print(json.dumps(matches, indent=2))

    elif len(sys.argv) > 1 and sys.argv[1] == "--dispatch":
        files = [Path(f) for f in sys.argv[2:]]
        if not files:
            import subprocess
            r = subprocess.run(
                ["git", "diff", "--name-only", "HEAD~1"],
                capture_output=True, text=True, cwd=str(ROOT_DIR),
                encoding="utf-8", errors="replace",
            )
            files = [ROOT_DIR / f.strip() for f in r.stdout.splitlines() if f.strip()]
        count = dispatch_antipattern_check(files)
        print(f"Dispatched {count} CHECK_ANTIPATTERN tasks")

    else:
        print("Usage:")
        print("  --check <text|file>    Synchronous anti-pattern check (LLM)")
        print("  --dispatch [files...]  Post [CHECK_ANTIPATTERN] tasks to Swarm")
        print("                         (no files = auto-detect from git diff)")
