import json
import threading
from datetime import datetime, timezone, timedelta
from pathlib import Path

from pulse.core.paths import get_state_dir
from pulse.core import event_bus
from pulse.core.brain_db import get_conn

MEMORY_DIR = get_state_dir() / "memory"
LABILE_ITEMS_FILE = MEMORY_DIR / "labile_items.json"
TTL_SECONDS = 30

# Whitelist of valid DB tables — prevents SQL injection via f-string table names
_VALID_TABLES = frozenset({"lessons", "failures", "patterns", "facts",
                           "schemas", "procedures", "decisions", "episodic"})

_lock = threading.RLock()
_labile_state = {}

def _load_labile():
    global _labile_state
    with _lock:
        if not LABILE_ITEMS_FILE.exists():
            _labile_state = {}
            return
        try:
            with open(LABILE_ITEMS_FILE, "r", encoding="utf-8") as f:
                _labile_state = json.load(f)
        except Exception:
            _labile_state = {}

def _save_labile():
    with _lock:
        MEMORY_DIR.mkdir(parents=True, exist_ok=True)
        with open(LABILE_ITEMS_FILE, "w", encoding="utf-8") as f:
            json.dump(_labile_state, f, indent=2)

def mark_labile(item_id: str, table: str):
    """Marks an item as labile (open to modification) with a 30s TTL."""
    if table not in _VALID_TABLES:
        return  # Reject unknown table names (SQL injection prevention)
    with _lock:
        if not _labile_state:
            _load_labile()
            
        _labile_state[item_id] = {
            "table": table,
            "expires_at": (datetime.now(timezone.utc) + timedelta(seconds=TTL_SECONDS)).isoformat()
        }
        _save_labile()

def reinforce(item_id: str):
    """Reinforce a labile item (salience += 0.5, reset decay)."""
    with _lock:
        if not _labile_state:
            _load_labile()
            
        if item_id not in _labile_state:
            return False # Not labile
            
        entry = _labile_state[item_id]
        if datetime.fromisoformat(entry["expires_at"]) < datetime.now(timezone.utc):
            del _labile_state[item_id]
            _save_labile()
            return False
            
        # Perform DB update (validate table against whitelist)
        conn = get_conn()
        table = entry["table"]
        if table not in _VALID_TABLES:
            del _labile_state[item_id]
            _save_labile()
            return False
        with conn:
            conn.execute(f"""
                UPDATE {table}
                SET salience = MIN(salience + 0.5, 7.0),
                    last_accessed = ?,
                    access_count = access_count + 1
                WHERE id = ?
            """, (datetime.now(timezone.utc).isoformat(), item_id))
            
        event_bus.publish("memory.reconsolidation.reinforce", "reconsolidation", {"item_id": item_id})
        del _labile_state[item_id]
        _save_labile()
        return True

def contradict(item_id: str):
    """Contradict a labile item (confidence -= 0.1, flag)."""
    with _lock:
        if not _labile_state:
            _load_labile()
            
        if item_id not in _labile_state:
            return False
            
        entry = _labile_state[item_id]
        if datetime.fromisoformat(entry["expires_at"]) < datetime.now(timezone.utc):
            del _labile_state[item_id]
            _save_labile()
            return False
            
        conn = get_conn()
        table = entry["table"]
        if table not in _VALID_TABLES:
            del _labile_state[item_id]
            _save_labile()
            return False
        with conn:
            conn.execute(f"""
                UPDATE {table}
                SET confidence = MAX(confidence - 0.1, 0.0),
                    validity = CASE WHEN confidence - 0.1 <= 0.0 THEN 'contested' ELSE validity END
                WHERE id = ?
            """, (item_id,))
            
        event_bus.publish("memory.reconsolidation.contradict", "reconsolidation", {"item_id": item_id})
        del _labile_state[item_id]
        _save_labile()
        return True

def _sweep_expired():
    """Clean up items past the 30s window."""
    with _lock:
        if not _labile_state:
            _load_labile()
        now = datetime.now(timezone.utc)
        expired = [k for k, v in _labile_state.items() if datetime.fromisoformat(v["expires_at"]) < now]
        if expired:
            for k in expired:
                del _labile_state[k]
            _save_labile()
