#!/usr/bin/env python3
# Copyright (c) 2026 PULSE Contributors. Proprietary. All rights reserved.
"""Anti-patterns search logic."""
import logging
from pathlib import Path

from pulse.core.brain_utils import (load_json_dict, search_relevance, ANTI_PATTERNS_DIR)
from pulse.brain.index.search_utils import compute_salience, days_since, get_retrieval_count

log = logging.getLogger("pulse.search.anti_patterns")


class _TableEmpty(Exception):
    """Raised when the SQLite table has 0 rows — signals caller to fall back."""


def _search_anti_patterns_sqlite(query: str, config: dict) -> list:
    """Primary path: SQLite query against anti_patterns table.

    Raises _TableEmpty if the table doesn't exist or has 0 rows.
    Phase 4 will create and populate the anti_patterns table; until then
    this always raises _TableEmpty, triggering the flat-file fallback.
    """
    from pulse.core.brain_db import get_conn

    conn = get_conn()

    # Check if anti_patterns table exists and has rows
    try:
        row_count = conn.execute(
            "SELECT COUNT(*) FROM anti_patterns"
        ).fetchone()[0]
    except Exception:
        # Table doesn't exist yet — fall back
        raise _TableEmpty()

    if row_count == 0:
        raise _TableEmpty()

    query_lower = query.lower().strip()
    if not query_lower:
        return []

    words = query_lower.split()
    if not words:
        return []

    # FTS5 candidate retrieval (OR for broad recall, score in Python)
    _STOP = {"the","a","an","is","are","was","were","be","to","of","in","for","on","with","at","by","from","as","it","this","that","not","no","but","or","and","if","so"}
    words = [w for w in words if len(w) >= 3 and w.lower() not in _STOP]
    if not words: return []
    _joiner = " OR "
    fts_expr = _joiner.join(words[:10])

    try:
        candidates = conn.execute("""
            SELECT f.item_id, f.content
            FROM knowledge_fts f
            WHERE f.table_name = 'anti_patterns'
              AND knowledge_fts MATCH ?
            LIMIT 50
        """, (fts_expr,)).fetchall()
    except Exception as e:
        log.debug("FTS5 anti_patterns query failed: %s", e)
        return []

    if not candidates:
        return []

    id_list = [c["item_id"] for c in candidates]
    placeholders = ",".join("?" for _ in id_list)
    rows = conn.execute(
        f"SELECT * FROM anti_patterns WHERE id IN ({placeholders})", id_list
    ).fetchall()

    rows_by_id = {r["id"]: r for r in rows}
    results = []

    for cand in candidates:
        row = rows_by_id.get(cand["item_id"])
        if not row:
            continue

        desc = row["description"] or ""
        rule = row["rule"] or ""
        content = f"{desc} {rule}".strip() if rule else desc
        sim = search_relevance(query, content)
        if sim < 0.2:
            continue

        days = days_since(row["created"] or "")
        results.append({
            "source": "anti_patterns",
            "id": row["id"],
            "table_name": "anti_patterns",
            "title": f"[ACTIVE] {desc[:60]}",
            "text": (rule[:500] if rule else desc[:500]),
            "content": content[:500],
            "relevance": round(sim, 3),
            "salience": compute_salience(sim, days, config,
                                         retrieval_count=get_retrieval_count(content[:200])),
            "severity": "medium",
            "evidence_count": int(row["evidence_count"] or 0),
            "access_count": int(row["access_count"] or 0) if "access_count" in row.keys() else 0,
            "confidence": float(row["confidence"] or 0.5),
        })

    return sorted(results, key=lambda x: -x["salience"])


def _search_anti_patterns_flatfile(query: str, config: dict) -> list:
    """Flat-file fallback search."""
    results = []

    try:
        from pulse.core.brain_db import get_active_anti_patterns
        active = get_active_anti_patterns()
    except Exception:
        active = []
    if active:
        for ap in active:
            if ap.get("status") != "active":
                continue
            desc = ap.get("description", "")
            rule = ap.get("rule", "")
            keywords = " ".join(ap.get("keywords", []))
            search_text = f"{desc} {rule} {keywords}"
            sim = search_relevance(query, search_text)
            if sim >= 0.2:
                days = days_since(ap.get("created", ""))
                results.append({
                    "source": "anti_patterns",
                    "title": f"[ACTIVE] {desc[:60]}",
                    "text": rule[:500] if rule else desc[:500],
                    "relevance": round(sim, 3),
                    "salience": compute_salience(sim, days, config,
                                                 retrieval_count=get_retrieval_count(desc[:200])),
                    "severity": ap.get("severity", "medium"),
                    "evidence_count": ap.get("evidence_count", 0),
                })

    reflex_file = ANTI_PATTERNS_DIR / "reflex_patterns.json"
    if reflex_file.exists():
        reflex_data = load_json_dict(reflex_file)
        for rx in (reflex_data.get("entries", [])
                   if isinstance(reflex_data, dict) else []):
            if rx.get("status") != "active":
                continue
            desc = rx.get("description", "")
            sim = search_relevance(query, f"{desc} {rx.get('pattern_name', '')}")
            if sim >= 0.2:
                days = days_since(rx.get("timestamp", ""))
                results.append({
                    "source": "reflex_arc",
                    "title": f"[REFLEX] {desc[:60]}",
                    "text": desc[:500],
                    "relevance": round(sim, 3),
                    "salience": compute_salience(sim, days, config,
                                                 retrieval_count=get_retrieval_count(desc[:200])),
                    "severity": rx.get("severity", "medium"),
                })

    return sorted(results, key=lambda x: -x["salience"])


def search_anti_patterns(query: str, config: dict) -> list:
    """Search anti-patterns matching query.

    Primary: SQLite query against `anti_patterns` table (fast indexed lookup).
    Fallback: flat-file read of JSON files if SQLite table is empty/missing.

    Returns list of hit dicts compatible with brain_search format.
    """
    try:
        return _search_anti_patterns_sqlite(query, config)
    except _TableEmpty:
        log.debug("SQLite anti_patterns table empty/missing, falling back to flat-file")
    except Exception as e:
        log.debug("SQLite anti_patterns search failed, falling back to flat-file: %s", e)

    # Fallback: flat-file read (graceful degradation)
    return _search_anti_patterns_flatfile(query, config)
