#!/usr/bin/env python3
# Copyright (c) 2026 PULSE Contributors. Proprietary. All rights reserved.
"""
D22: Reconsolidation & Extinction Daemon

Implements Neuroscience Gap 3 from QR8. When a 'CONTRADICTS' edge is
added to the knowledge graph, this daemon traces back to the original node
and programmatically reduces its confidence, effectively allowing the brain
to "un-learn" outdated or incorrect information.
"""
import json
import logging
import time
from pathlib import Path
import sys

# Setup paths and logging
SCRIPT_DIR = Path(__file__).resolve().parent
ROOT_DIR = SCRIPT_DIR.parents[3]
if str(ROOT_DIR) not in sys.path:
    sys.path.insert(0, str(ROOT_DIR))

from pulse.core.brain_db import get_conn
from pulse.core.brain_utils import load_json_dict, save_json

log = logging.getLogger("pulse.d22_reconsolidation")
STATE_FILE = ROOT_DIR / "state" / "reconsolidation_state.json"
TABLE_PREFIX_MAP = {"L": "lessons", "FA": "facts", "P": "patterns", "F": "failures"}

def _load_state() -> dict:
    """Load the last processed edge ID."""
    return load_json_dict(STATE_FILE, default={"last_processed_edge_id": 0})

def _save_state(state: dict):
    """Save the last processed edge ID."""
    save_json(STATE_FILE, state)

def _get_new_contradictions(last_id: int) -> list:
    """Fetch new 'CONTRADICTS' edges from the graph."""
    try:
        conn = get_conn()
        rows = conn.execute(
            """
            SELECT id, from_node FROM graph_edges
            WHERE edge_type = 'CONTRADICTS' AND id > ?
            ORDER BY id ASC
            LIMIT 100
            """,
            (last_id,),
        ).fetchall()
        return rows
    except Exception as e:
        log.error(f"Failed to fetch contradictions: {e}")
        return []

def _demote_knowledge_item(item_id: str) -> bool:
    """Finds the item in its table and reduces its confidence."""
    prefix = item_id.split("-")[0]
    table = TABLE_PREFIX_MAP.get(prefix)
    if not table:
        log.warning(f"Unknown item prefix '{prefix}' for item {item_id}. Cannot demote.")
        return False

    try:
        conn = get_conn()
        with conn:
            cursor = conn.execute(
                f"""
                UPDATE {table}
                SET
                    confidence = confidence * 0.5,
                    title = CASE
                        WHEN title NOT LIKE '[SUPERSEDED] %' THEN '[SUPERSEDED] ' || title
                        ELSE title
                    END
                WHERE id = ? AND title NOT LIKE '%[DEMOTED]%'
                """,
                (item_id,),
            )
        if cursor.rowcount > 0:
            log.info(f"Reconsolidation: Demoted '{item_id}' in table '{table}'.")
            return True
        return False
    except Exception as e:
        log.error(f"Failed to demote knowledge item {item_id}: {e}")
        return False

def run_once(verbose: bool = False) -> dict:
    """Run one cycle of the reconsolidation daemon."""
    state = _load_state()
    last_processed_id = state.get("last_processed_edge_id", 0)
    
    contradictions = _get_new_contradictions(last_processed_id)
    if not contradictions:
        if verbose:
            log.info("No new contradictions found.")
        return {"status": "no_new_edges", "processed_count": 0}

    processed_count = 0
    max_id = last_processed_id
    for edge_id, from_node in contradictions:
        if _demote_knowledge_item(from_node):
            processed_count += 1
        max_id = max(max_id, edge_id)

    if max_id > last_processed_id:
        _save_state({"last_processed_edge_id": max_id})

    log.info(f"Completed reconsolidation cycle. Processed {processed_count} contradictions.")
    return {"status": "ok", "processed_count": processed_count}

def run_daemon_loop(poll_interval: int = 60):
    """Run the daemon in a continuous loop."""
    log.info("D22 Reconsolidation Daemon started.")
    while True:
        try:
            run_once(verbose=True)
        except Exception as e:
            log.error(f"Reconsolidation daemon loop error: {e}", exc_info=True)
        time.sleep(poll_interval)

if __name__ == "__main__":
    logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
    # Run once if called with an argument, otherwise loop
    if len(sys.argv) > 1:
        run_once(verbose=True)
    else:
        run_daemon_loop()
