#!/usr/bin/env python3
# Copyright (c) 2026 PULSE Contributors. Proprietary. All rights reserved.
# See LICENSE file for details.
"""Gate implementations for the anti-pattern blocking hook (gates 1,2,4,5)."""

import re
import sqlite3
from pathlib import Path

_CONFIDENCE_THRESHOLD = 0.8
_EVIDENCE_THRESHOLD = 5
_FAILURE_CONFIDENCE_THRESHOLD = 0.90


from pulse.brain.integrity.gate_ap_query import (
    _HARD_BLOCK_THRESHOLD, _SOFT_BLOCK_THRESHOLD,
    _query_anti_patterns, _format_ap_block, _is_relevant_to_content, _graded_gate_result,
)

_CONFIDENCE_THRESHOLD = 0.8
_EVIDENCE_THRESHOLD = 5
_FAILURE_CONFIDENCE_THRESHOLD = 0.90

_STOP_TOKENS: frozenset = frozenset({
    "pulse", "brain", "agent", "agents", "worker", "workers",
    "daemon", "daemons", "admin", "graph", "api", "cli", "web",
    "core", "main", "test", "tests", "setup", "utils", "util",
    "base", "init", "load", "save", "read", "write", "data",
    "info", "file", "path", "code", "func", "type", "class",
    "self", "none", "true", "false", "return", "import", "from",
    "pass", "raise", "print", "error", "python", "json", "yaml",
    "toml", "text", "string", "bytes", "anti", "pattern", "gate",
    "hook", "query", "search", "match", "config", "settings",
    "result", "results", "output", "input", "value", "values",
    "items", "keys", "lines", "token", "tokens",
})


def _keyword_overlap(text: str, keywords: list) -> bool:
    """True if any keyword (len >= 9) appears as a whole word in text."""
    if not text or not keywords:
        return False
    low = text.lower()
    for kw in keywords:
        if len(kw) < 9:
            continue
        idx = low.find(kw)
        while idx != -1:
            pre_ok = idx == 0 or not (low[idx - 1].isalnum() or low[idx - 1] == "_")
            end = idx + len(kw)
            post_ok = end >= len(low) or not (low[end].isalnum() or low[end] == "_")
            if pre_ok and post_ok:
                return True
            idx = low.find(kw, idx + 1)
    return False

def _extract_keywords(content: str) -> list:
    """Extract up to 12 specific identifiers from content being written."""
    if not content:
        return []
    raw = re.findall(r"\b([a-z][a-z0-9_]{4,})\b", content.lower())
    seen: set = set()
    result: list = []
    for tok in raw:
        if tok not in _STOP_TOKENS and tok not in seen:
            seen.add(tok)
            result.append(tok)
        if len(result) >= 12:
            break
    return result

def _get_domain_from_path(file_path: str) -> str:
    """Derive a coarse domain string from a file path."""
    if not file_path:
        return "general"
    parts = Path(file_path).parts
    for part in parts:
        if part not in ("pulse", ".", "..") and not part.endswith(".py"):
            return part.lower()
    return "general"

def _open_db(db_path: Path):
    conn = sqlite3.connect(str(db_path.resolve()), timeout=3)
    conn.row_factory = sqlite3.Row
    return conn



def _gate1_check(keywords: list, content: str, conn, file_path: str = ""):
    """Internal: return (blocked, msg_or_None, graded_or_None)."""
    db_path = conn if isinstance(conn, Path) else _resolve_db_path()
    results = _query_anti_patterns(db_path, keywords)
    for row in results:
        rule = row.get("rule") or row.get("description") or ""
        if not _is_relevant_to_content(rule, content, file_path):
            continue
        confidence = float(row.get("confidence") or 0.0)
        evidence_count = int(row.get("evidence_count") or 0)
        # Item 98: Severity multiplier — NEVER rules are 2x more dangerous
        severity = 2.0 if "NEVER" in (rule or "").upper() and evidence_count >= 3 else 1.0
        effective_conf = confidence * severity
        if effective_conf >= _CONFIDENCE_THRESHOLD or evidence_count >= _EVIDENCE_THRESHOLD:
            msg = _format_ap_block(row)
            graded = _graded_gate_result(min(effective_conf, 1.0), msg)
            blocked = graded["level"] in ("hard_block", "soft_block")
            return blocked, msg, graded
    return False, None, None


def gate1_anti_pattern(keywords: list, content: str, conn, file_path: str = "") -> tuple:
    """Return (blocked, message_or_None). Backward-compatible 2-tuple.

    For graded inhibition (N50), use gate1_graded() which returns a 3-level dict.
    """
    blocked, msg, _graded = _gate1_check(keywords, content, conn, file_path)
    return blocked, msg


def gate1_graded(keywords: list, content: str, conn, file_path: str = "") -> dict | None:
    """N50: Return graded inhibition dict or None if no match.

    Dict keys: level (hard_block|soft_block|advisory), confidence, message.
    """
    _blocked, _msg, graded = _gate1_check(keywords, content, conn, file_path)
    return graded


def gate2_failure_scan(keywords: list, conn, content: str = "", file_path: str = "") -> tuple:
    """Return (blocked, failure_content_or_None)."""
    try:
        db_path = conn if isinstance(conn, Path) else None
        if db_path is not None:
            if not keywords or not db_path.exists():
                return False, None
            connection = _open_db(db_path)
        else:
            if not keywords:
                return False, None
            connection = conn
        rows = connection.execute(
            "SELECT content, confidence FROM failures "
            "WHERE confidence > ? AND source_type != 'CONTESTED' "
            "ORDER BY confidence DESC LIMIT 40",
            (_FAILURE_CONFIDENCE_THRESHOLD,),
        ).fetchall()
        if db_path is not None:
            connection.close()
        for row in rows:
            if _keyword_overlap(row["content"] or "", keywords):
                if not content or _is_relevant_to_content(row["content"] or "", content, file_path):
                    return True, row["content"]
        return False, None
    except Exception:
        return False, None


def gate4_decision_conflict(keywords: list, conn) -> tuple:
    """Return (blocked, decision_content_or_None)."""
    try:
        db_path = conn if isinstance(conn, Path) else None
        if db_path is not None:
            if not keywords or not db_path.exists():
                return False, None
            connection = _open_db(db_path)
        else:
            if not keywords:
                return False, None
            connection = conn
        rows = connection.execute(
            "SELECT content FROM decisions "
            "WHERE source_type = 'CONTESTED' OR validity = 'contested' "
            "LIMIT 50",
        ).fetchall()
        if db_path is not None:
            connection.close()
        for row in rows:
            if _keyword_overlap(row["content"] or "", keywords):
                return True, row["content"]
        return False, None
    except Exception:
        return False, None


def gate5_temporal_advisory(keywords: list, conn) -> list:
    """Return list of prerequisite texts. Never blocks — advisory only."""
    try:
        db_path = conn if isinstance(conn, Path) else None
        if db_path is not None:
            if not keywords or not db_path.exists():
                return []
            connection = _open_db(db_path)
        else:
            if not keywords:
                return []
            connection = conn
        advisories = []
        for kw in keywords[:3]:
            rows = connection.execute(
                "SELECT gn.text FROM graph_edges ge "
                "JOIN graph_nodes gn ON ge.from_node = gn.id "
                "JOIN graph_nodes gn2 ON ge.to_node = gn2.id "
                "WHERE gn2.text LIKE ? "
                "AND ge.edge_type = 'TEMPORAL' AND ge.confidence > 0.8 "
                "LIMIT 3",
                (f"%{kw}%",),
            ).fetchall()
            for r in rows:
                txt = r[0] if isinstance(r, (list, tuple)) else r["text"]
                if txt and txt not in advisories:
                    advisories.append(txt)
            if len(advisories) >= 5:
                break
        if db_path is not None:
            connection.close()
        return advisories
    except Exception:
        return []


def _resolve_root() -> Path:
    return Path(__file__).resolve().parent.parent.parent

def _resolve_db_path() -> Path:
    from pulse.core.paths import get_brain_dir
    return get_brain_dir() / "pulse_brain.db"
