# Copyright (c) 2026 PULSE Contributors. Proprietary. All rights reserved.
"""Grounding: promote/demote knowledge items based on production evidence.

CONVERSATIONAL → CONFIRMED when independent evidence signals converge.
CONFIRMED → CONTESTED when contradiction detected.

Criteria (any ONE promotes): reward credit >= 0.40 with relevance >= 0.5,
  retrieval count >= 5, access_count >= 3, or hard-evidence triggers
  (test_pass, task_success, deployment, manual_confirm).

ground_events: append-only JSON array in metadata, capped at 20 entries.
"""
import json
import logging
from datetime import datetime, timezone

log = logging.getLogger("pulse.brain.grounding")

# Thresholds
RETRIEVAL_THRESHOLD = 5
REINFORCE_THRESHOLD = 3
CREDIT_THRESHOLD = 0.40
RELEVANCE_GATE = 0.5   # EC2: minimum relevance to accept reward promotion
MAX_GROUND_EVENTS = 20  # EC5: cap ground_events array

_VALID_TABLES = frozenset({"lessons", "failures", "patterns", "facts",
                           "schemas", "procedures", "decisions", "episodic",
                           "intents", "anti_patterns", "evidence", "private"})
# Hard-evidence triggers that always pass criteria (no threshold needed)
_HARD_TRIGGERS = frozenset({"test_pass", "task_success", "deployment", "manual_confirm"})


def maybe_promote(item_id: str, table: str, trigger: str, evidence: dict | None = None) -> bool:
    """Promote item to CONFIRMED if grounding criteria met. Atomic SQL update."""
    if table not in _VALID_TABLES:
        return False
    evidence = evidence or {}
    # EC2: gate reward promotions on relevance
    if trigger == "reward" and evidence.get("relevance", 1.0) < RELEVANCE_GATE:
        return False
    try:
        from pulse.core.brain_db import get_conn
        conn = get_conn()
        row = conn.execute(
            f"SELECT metadata, validity, access_count FROM {table} WHERE id = ?", (item_id,)
        ).fetchone()
        if not row or (row["validity"] or "valid") == "contested":
            return False
        meta = json.loads(row["metadata"] or "{}") if row["metadata"] else {}
        if meta.get("source_type") == "CONFIRMED":
            return False
        if not _check_criteria(row["access_count"] or 0, evidence, trigger):
            return False
        # Build event + cap at MAX_GROUND_EVENTS (EC5)
        event = {"ts": datetime.now(timezone.utc).isoformat(), "trigger": trigger, "evidence": evidence}
        events = meta.get("ground_events", [])
        events.append(event)
        meta["ground_events"] = events[-MAX_GROUND_EVENTS:]
        meta["ground_event_count"] = meta.get("ground_event_count", 0) + 1
        meta["source_type"] = "CONFIRMED"
        # EC3: atomic write — single UPDATE, no read-modify-write race
        # P2-2: Bump confidence to at least 0.80 when grounding to CONFIRMED
        now = datetime.now(timezone.utc).isoformat()
        with conn:
            conn.execute(
                f"UPDATE {table} SET metadata = ?, updated_at = ?, source_type = 'CONFIRMED', "
                f"confidence = MAX(confidence, 0.80) WHERE id = ?",
                (json.dumps(meta, ensure_ascii=False), now, item_id)
            )
        log.info("Grounded %s (table=%s trigger=%s)", item_id, table, trigger)
        return True
    except Exception as e:
        log.warning("maybe_promote failed for %s: %s", item_id, e)
        return False


def maybe_demote(item_id: str, table: str, reason: str = "contradiction") -> bool:
    """EC1: Demote CONFIRMED → CONTESTED when knowledge becomes wrong."""
    if table not in _VALID_TABLES:
        return False
    try:
        from pulse.core.brain_db import get_conn
        conn = get_conn()
        row = conn.execute(f"SELECT metadata FROM {table} WHERE id = ?", (item_id,)).fetchone()
        if not row:
            return False
        meta = json.loads(row["metadata"] or "{}") if row["metadata"] else {}
        if meta.get("source_type") not in ("CONFIRMED", "OBSERVED"):
            return False
        event = {"ts": datetime.now(timezone.utc).isoformat(), "trigger": "demotion", "evidence": {"reason": reason}}
        events = meta.get("ground_events", [])
        events.append(event)
        meta["ground_events"] = events[-MAX_GROUND_EVENTS:]
        meta["ground_event_count"] = meta.get("ground_event_count", 0) + 1
        meta["source_type"] = "CONTESTED"
        with conn:
            conn.execute(
                f"UPDATE {table} SET metadata = ?, source_type = 'CONTESTED' WHERE id = ?",
                (json.dumps(meta, ensure_ascii=False), item_id)
            )
        log.info("Demoted %s to CONTESTED (table=%s reason=%s)", item_id, table, reason)
        return True
    except Exception as e:
        log.warning("maybe_demote failed for %s: %s", item_id, e)
        return False


def _check_criteria(access_count: int, evidence: dict, trigger: str) -> bool:
    """Return True if at least one grounding criterion is met."""
    if trigger in _HARD_TRIGGERS:
        return True
    if trigger == "reward" and evidence.get("credit", 0.0) >= CREDIT_THRESHOLD:
        return True
    if access_count >= REINFORCE_THRESHOLD:
        return True
    if trigger == "retrieval" and evidence.get("retrieval_count", 0) >= RETRIEVAL_THRESHOLD:
        return True
    return False
