#!/usr/bin/env python3
# Copyright (c) 2026 PULSE Contributors. Proprietary. All rights reserved.
"""
Episodic Memory Writer: Stores session-level memories in the episodic table.

Each episode captures what happened in a conversation session — agent, summary,
key entities/events, domain. Confidence is 0.85 (direct observation).

Dependencies: brain_db, brain_utils (Law 4)
"""

import hashlib
import json
import logging
from datetime import datetime, timezone
from typing import List, Optional

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


def _make_episode_id(agent: str, timestamp: str, summary: str) -> str:
    """Generate a stable episode ID from agent + timestamp + summary prefix."""
    raw = f"{agent}:{timestamp}:{summary[:60]}".lower().strip()
    return f"EP-{hashlib.md5(raw.encode('utf-8')).hexdigest()[:12]}"


def _extract_keywords(text: str) -> List[str]:
    """Extract simple keywords from summary text (top content words)."""
    stop = {
        "the", "a", "an", "is", "was", "are", "were", "be", "been",
        "to", "of", "in", "for", "on", "with", "at", "by", "from",
        "and", "or", "but", "not", "this", "that", "it", "its",
        "has", "had", "have", "do", "did", "does", "will", "would",
        "can", "could", "should", "may", "might", "shall",
    }
    words = text.lower().split()
    seen = set()
    keywords = []
    for w in words:
        w = w.strip(".,;:!?\"'()[]{}").strip()
        if len(w) >= 3 and w not in stop and w not in seen:
            seen.add(w)
            keywords.append(w)
        if len(keywords) >= 10:
            break
    return keywords


def save_episode(
    agent: str,
    session_summary: str,
    key_events: Optional[List[str]] = None,
    domain: str = "general",
    session_id: str = "",
    timestamp: Optional[str] = None,
) -> bool:
    """Write a session episode to the episodic table.

    Args:
        agent: Name of the agent that produced this episode.
        session_summary: Human-readable summary of what happened.
        key_events: Optional list of notable events/entities in the session.
        domain: Knowledge domain (default 'general').
        session_id: Optional session identifier.
        timestamp: ISO timestamp (defaults to now UTC).

    Returns:
        True if the episode was written, False on failure or skip.
    """
    if not session_summary or len(session_summary.strip()) < 15:
        return False

    ts = timestamp or datetime.now(timezone.utc).isoformat()
    ep_id = _make_episode_id(agent, ts, session_summary)
    entities = key_events or []
    keywords = _extract_keywords(session_summary)

    try:
        from pulse.core.brain_db import get_conn
        conn = get_conn()

        # Ensure confidence column exists (migration-safe)
        _has_confidence = any(
            r[1] == "confidence"
            for r in conn.execute("PRAGMA table_info(episodic)").fetchall()
        )
        if not _has_confidence:
            try:
                conn.execute(
                    "ALTER TABLE episodic ADD COLUMN confidence REAL DEFAULT 0.85"
                )
                conn.commit()
                _has_confidence = True
            except Exception:
                pass  # DB locked or column race — proceed without confidence

        with conn:
            if _has_confidence:
                conn.execute(
                    "INSERT OR IGNORE INTO episodic "
                    "(id, summary, entities, agent, domain, keywords, "
                    " timestamp, session_id, metadata, confidence) "
                    "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
                    (
                        ep_id,
                        session_summary.strip()[:500],
                        json.dumps(entities[:20]),
                        agent or "unknown",
                        domain or "general",
                        json.dumps(keywords),
                        ts,
                        session_id,
                        json.dumps({"source": "episodic_writer",
                                    "confidence": 0.85}),
                        0.85,
                    ),
                )
            else:
                # Fallback: insert without confidence column, store in metadata
                conn.execute(
                    "INSERT OR IGNORE INTO episodic "
                    "(id, summary, entities, agent, domain, keywords, "
                    " timestamp, session_id, metadata) "
                    "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
                    (
                        ep_id,
                        session_summary.strip()[:500],
                        json.dumps(entities[:20]),
                        agent or "unknown",
                        domain or "general",
                        json.dumps(keywords),
                        ts,
                        session_id,
                        json.dumps({"source": "episodic_writer",
                                    "confidence": 0.85}),
                    ),
                )
            inserted = conn.execute("SELECT changes()").fetchone()[0] > 0

        # Also index in FTS for search
        if inserted:
            try:
                with conn:
                    conn.execute(
                        "INSERT INTO knowledge_fts "
                        "(item_id, content, title, domain, agent, table_name) "
                        "VALUES (?, ?, ?, ?, ?, 'episodic')",
                        (
                            ep_id,
                            session_summary.strip()[:500],
                            f"[EPISODE] {session_summary.strip()[:60]}",
                            domain or "general",
                            agent or "unknown",
                        ),
                    )
            except Exception as e:
                log.debug("FTS index for episodic failed: %s", e)

        return inserted
    except Exception as e:
        log.warning("Failed to save episode: %s", e)
        return False


def build_session_summary(
    agent: str,
    learnings: List[str],
    failures: List[str],
    task_id: str = "",
    domain: str = "general",
) -> str:
    """Build a concise session summary from save() inputs.

    Returns a one-line summary suitable for episodic storage.
    """
    parts = []
    if task_id:
        parts.append(f"Task {task_id}")
    if learnings:
        n = len(learnings)
        first = learnings[0].strip()[:80] if learnings[0] else ""
        parts.append(f"{n} learning{'s' if n > 1 else ''}: {first}")
    if failures:
        n = len(failures)
        first = failures[0].strip()[:80] if failures[0] else ""
        parts.append(f"{n} failure{'s' if n > 1 else ''}: {first}")
    if not parts:
        parts.append("Interactive session (no explicit learnings)")
    return f"[{agent}] " + "; ".join(parts)
