#!/usr/bin/env python3
# Copyright (c) 2026 PULSE Contributors. Proprietary. All rights reserved.
"""
Session-Based Replay: Hippocampal replay of actual retrieval sequences (Phase 3).

Unlike associative_replay.py (which picks high-salience items for creative
juxtaposition), this replays the ACTUAL sequences of items that agents
retrieved during real task execution.

Algorithm:
1. Load completed sessions from state/completed/ (last 48h)
2. For sessions with task success: re-activate + reinforce items, strengthen edges
3. For sessions with task failure: gently weaken item salience (negative replay)
4. Use STDP + Hebbian infrastructure to strengthen temporal/co-activation edges

This is hippocampal replay — re-experiencing successful episodes during
offline consolidation to strengthen learned associations.
"""
import json
import logging
from datetime import datetime, timezone, timedelta
from pathlib import Path

from pulse.core.paths import get_state_dir

log = logging.getLogger("pulse.session_replay")

COMPLETED_DIR = get_state_dir() / "completed"
MAX_SESSIONS_PER_CYCLE = 10
MAX_ITEMS_PER_REPLAY = 15
NEGATIVE_SAL_DELTA = 0.1  # Gentle salience reduction for failed sessions


def run_session_replay(verbose=False) -> dict:
    """Main replay engine. Called during consolidation."""
    now = datetime.now(timezone.utc)
    cutoff = now - timedelta(hours=48)

    sessions = _load_recent_sessions(cutoff)
    if not sessions:
        if verbose:
            print("  [REPLAY] No recent sessions to replay")
        return {"replayed": 0, "status": "no_sessions"}

    replayed = 0
    reinforced = 0
    edges_created = 0

    for session in sessions[:MAX_SESSIONS_PER_CYCLE]:
        items = session.get("items", [])
        if len(items) < 2:
            continue

        task_id = session.get("task_id")
        success = _was_session_successful(task_id) if task_id else True

        # Sort items by access order
        items.sort(key=lambda x: x.get("first_accessed", ""))
        item_ids = _resolve_item_ids(items[:MAX_ITEMS_PER_REPLAY])

        if len(item_ids) < 2:
            continue

        # Replay: re-activate items
        if success:
            reinforced += _positive_replay(item_ids)
        else:
            _negative_replay(item_ids)

        # Strengthen/weaken temporal edges via STDP
        try:
            from pulse.brain.plasticity.stdp import record_retrieval, apply_stdp
            replay_session = f"replay_{session.get('session_id', 'unknown')}"
            for iid in item_ids:
                record_retrieval(replay_session, iid)
            edges_created += apply_stdp(replay_session, success)
        except Exception as e:
            log.debug("STDP replay failed: %s", e)

        # Hebbian co-activation
        try:
            from pulse.brain.plasticity.hebbian_knowledge import apply_hebbian
            _dt_sr = datetime.now(timezone.utc)
            _min_sr = (_dt_sr.minute // 5) * 5
            session_key = _dt_sr.strftime("%Y%m%d_%H") + f"_{_min_sr:02d}"
            from pulse.brain.plasticity.hebbian_knowledge import record_coactivation
            record_coactivation(item_ids)
            edges_created += apply_hebbian(session_key, success)
        except Exception as e:
            log.debug("Hebbian replay failed: %s", e)

        replayed += 1

    result = {
        "replayed": replayed,
        "reinforced": reinforced,
        "edges_created": edges_created,
        "sessions_scanned": len(sessions),
    }
    if verbose:
        print(f"  [REPLAY] {replayed} sessions replayed, "
              f"{reinforced} reinforced, {edges_created} edges updated")
    return result


def _load_recent_sessions(cutoff: datetime) -> list:
    """P47 FIX: Load sessions from SQLite sessions table (was state/completed/ which is never populated)."""
    try:
        from pulse.core.brain_db import get_conn
        conn = get_conn()
        cutoff_str = cutoff.isoformat()
        rows = conn.execute(
            "SELECT id, agent, started_at, status, summary, domain FROM sessions "
            "WHERE started_at > ? AND status IN ('completed','abandoned') ORDER BY started_at DESC LIMIT 20",
            (cutoff_str,)).fetchall()
        sessions = []
        for r in rows:
            session_id = r[0]
            # Fetch turns for this session to populate items[]
            turns = conn.execute(
                "SELECT query, result_count, tool_name, timestamp FROM session_turns "
                "WHERE session_id = ? ORDER BY timestamp ASC LIMIT 50",
                (session_id,)).fetchall()
            items = []
            for t in turns:
                text = t[0] or ""  # query text
                if text:
                    items.append({"text": text, "first_accessed": t[3] or ""})
            sessions.append({"id": session_id, "session_id": session_id,
                            "agent": r[1], "timestamp": r[2],
                            "status": r[3], "summary": r[4] or "", "items": items})
        return sessions
    except Exception:
        pass
    # Legacy fallback: state/completed/ directory
    if not COMPLETED_DIR.exists():
        return []
    sessions = []
    for f in COMPLETED_DIR.iterdir():
        if f.suffix != ".json":
            continue
        try:
            mtime = datetime.fromtimestamp(f.stat().st_mtime, tz=timezone.utc)
            if mtime < cutoff:
                continue
        except Exception:
            continue
        try:
            data = json.loads(f.read_text(encoding="utf-8"))
            if isinstance(data, dict) and data.get("items"):
                sessions.append(data)
        except Exception:
            continue
    sessions.sort(key=lambda s: s.get("started_at", ""), reverse=True)
    return sessions


def _was_session_successful(task_id: str) -> bool:
    """Check if the task associated with a session completed successfully."""
    if not task_id:
        return True
    try:
        from pulse.core.brain_utils import TASK_BOARD_FILE
        data = json.loads(TASK_BOARD_FILE.read_text(encoding="utf-8"))
        for dispatch in data.get("dispatches", []):
            for task in dispatch.get("tasks", []):
                if task.get("task_id") == task_id:
                    return task.get("status", "").lower() == "done"
    except Exception:
        pass
    return True  # Default assumption


def _resolve_item_ids(items: list) -> list:
    """Resolve session items to brain DB item IDs via FTS lookup."""
    item_ids = []
    try:
        from pulse.core.brain_db import get_conn
        conn = get_conn()
        for item in items:
            text = item.get("text", "")
            if not text or len(text) < 10:
                continue
            try:
                row = conn.execute(
                    "SELECT item_id FROM knowledge_fts WHERE content MATCH ? LIMIT 1",
                    (text[:50],)
                ).fetchone()
                if row and row["item_id"]:
                    item_ids.append(row["item_id"])
            except Exception:
                pass
    except Exception as e:
        log.debug("Item ID resolution failed: %s", e)
    return item_ids


def _positive_replay(item_ids: list) -> int:
    """Re-activate and reinforce items from successful sessions."""
    reinforced = 0
    try:
        from pulse.brain.plasticity.reconsolidation import mark_labile, reinforce
        from pulse.core.brain_db import get_conn
        conn = get_conn()
        for item_id in item_ids:
            try:
                row = conn.execute(
                    "SELECT table_name FROM knowledge_fts WHERE item_id = ? LIMIT 1",
                    (item_id,)
                ).fetchone()
                if row:
                    mark_labile(item_id, row["table_name"], prediction_error=0.1)
                    if reinforce(item_id):
                        reinforced += 1
            except Exception:
                pass
    except Exception as e:
        log.debug("Positive replay failed: %s", e)
    return reinforced


def _negative_replay(item_ids: list):
    """Weaken items from failed sessions (gentle — salience reduction only)."""
    try:
        from pulse.core.brain_db import get_conn
        conn = get_conn()
        with conn:
            for item_id in item_ids:
                for table in ["lessons", "failures", "patterns"]:
                    conn.execute(
                        f"UPDATE {table} SET salience = MAX(salience - ?, 0.5) WHERE id = ?",
                        (NEGATIVE_SAL_DELTA, item_id)
                    )
    except Exception as e:
        log.debug("Negative replay failed: %s", e)
