"""Phase 6A: Associative Replay — consolidation-as-dreaming with random juxtaposition."""
import json
import time
from datetime import datetime, timezone, timedelta
from pathlib import Path
from pulse.core.paths import get_state_dir, get_brain_dir
from pulse.core.brain_db import get_conn
from pulse.core import event_bus

REPLAY_DIR = get_state_dir() / "replay_logs"


def run_replay(verbose=False) -> dict:
    """
    Main dreaming engine. Called by orchestrator during low-activity.
    1. Query top-20 highest-salience items from last 48h
    2. Partition into episodes (30-min windows)
    3. Random juxtaposition across domains
    4. Score via LLM (max 3 calls, budget-capped)
    5. Novel connections -> graph edges (type=ASSOCIATIVE)
    Returns replay session summary.
    """
    conn = get_conn()
    now = datetime.now(timezone.utc)
    cutoff = (now - timedelta(hours=48)).isoformat()

    # Step 1: Get top-20 recent high-salience items
    items = []
    for table in ["lessons", "failures", "patterns"]:
        rows = conn.execute(f"""
            SELECT id, content, title, domain, salience, timestamp
            FROM {table}
            WHERE timestamp > ? AND validity = 'valid'
            ORDER BY salience DESC LIMIT 7
        """, (cutoff,)).fetchall()
        items.extend([dict(r) for r in rows])

    items.sort(key=lambda x: x.get("timestamp", ""))
    items = items[:20]

    if len(items) < 4:
        return {"items_replayed": len(items), "status": "insufficient_data"}

    # Step 2: Partition into episodes (30-min windows)
    episodes = _partition_episodes(items)

    # Step 3: Cross-domain juxtapositions
    juxtapositions = []
    for episode in episodes:
        domains = set(i.get("domain", "general") for i in episode)
        if len(domains) < 2:
            continue
        # Pair items from different domains
        for i, item_a in enumerate(episode):
            for item_b in episode[i + 1:]:
                if item_a.get("domain") != item_b.get("domain"):
                    juxtapositions.append((item_a, item_b))

    # Step 4: Score juxtapositions (top 3 by combined salience, budget-capped)
    juxtapositions.sort(
        key=lambda p: p[0].get("salience", 0) + p[1].get("salience", 0),
        reverse=True,
    )
    scored = []
    llm_calls = 0

    for item_a, item_b in juxtapositions[:5]:
        if llm_calls >= 3:
            break
        # Try LLM scoring
        score_result = _score_juxtaposition(item_a, item_b)
        if score_result:
            llm_calls += 1
            scored.append(score_result)

    # Step 5: Add novel connections to graph
    new_edges = 0
    for s in scored:
        if s.get("combined_score", 0) > 0.6:
            try:
                with conn:
                    conn.execute("""
                        INSERT INTO graph_edges (from_node, to_node, edge_type, confidence, metadata)
                        VALUES (?, ?, 'ASSOCIATIVE', ?, ?)
                        ON CONFLICT(from_node, edge_type, to_node) DO NOTHING
                    """, (
                        s["item_a_id"], s["item_b_id"], s["combined_score"],
                        json.dumps({"source": "associative_replay", "reasoning": s.get("reasoning", "")}),
                    ))
                new_edges += 1
            except Exception:
                pass

    # Save replay log
    session = {
        "session_date": now.isoformat(),
        "items_replayed": len(items),
        "episodes_formed": len(episodes),
        "juxtapositions_scored": len(scored),
        "new_edges_added": new_edges,
        "llm_calls": llm_calls,
    }
    _save_replay_log(session)

    if verbose:
        print(f"  [REPLAY] {len(items)} items, {len(episodes)} episodes, {new_edges} new edges")

    event_bus.publish("replay.complete", "associative_replay", session)
    return session


def _partition_episodes(items: list, window_minutes: int = 30) -> list:
    """Group consecutive items within time window into episodes."""
    if not items:
        return []
    episodes = [[items[0]]]
    for item in items[1:]:
        try:
            prev_ts = datetime.fromisoformat(episodes[-1][-1].get("timestamp", ""))
            curr_ts = datetime.fromisoformat(item.get("timestamp", ""))
            if (curr_ts - prev_ts).total_seconds() <= window_minutes * 60:
                episodes[-1].append(item)
            else:
                episodes.append([item])
        except Exception:
            episodes.append([item])
    return [ep for ep in episodes if len(ep) >= 2]


def _score_juxtaposition(item_a: dict, item_b: dict) -> dict:
    """Score novelty via LLM (budget-capped). Falls back to heuristic."""
    try:
        from pulse.core.llm_router import dispatch
        prompt = (
            f"Rate the novelty of this connection (0-1):\n"
            f"Domain A ({item_a.get('domain')}): {item_a.get('title', '')[:100]}\n"
            f"Domain B ({item_b.get('domain')}): {item_b.get('title', '')[:100]}\n"
            f"Reply with ONLY a number between 0 and 1."
        )
        result = dispatch(prompt, task_type="fast")
        score = float(result.strip()[:4])
        score = max(0.0, min(1.0, score))
        return {
            "item_a_id": item_a["id"], "item_b_id": item_b["id"],
            "combined_score": score, "reasoning": "llm_scored",
        }
    except Exception:
        # Heuristic fallback: cross-domain bonus
        cross = 0.5 if item_a.get("domain") != item_b.get("domain") else 0.3
        return {
            "item_a_id": item_a["id"], "item_b_id": item_b["id"],
            "combined_score": cross, "reasoning": "heuristic_fallback",
        }


def _save_replay_log(session: dict):
    """Persist replay session log."""
    REPLAY_DIR.mkdir(parents=True, exist_ok=True)
    date_str = datetime.now(timezone.utc).strftime("%Y-%m-%d")
    log_file = REPLAY_DIR / f"replay_{date_str}.json"
    log_file.write_text(json.dumps(session, indent=2), encoding="utf-8")
