"""Nexus v2 — Timmy's persistent conversational awareness space.

Extends the v1 Nexus (chat + memory sidebar + teaching panel) with:

- **Persistent conversations** — SQLite-backed history survives restarts.
- **Introspection panel** — live cognitive state, recent thoughts, session
  analytics rendered alongside every conversation turn.
- **Sovereignty pulse** — real-time sovereignty health badge in the sidebar.
- **WebSocket** — pushes introspection + sovereignty snapshots so the
  Nexus page stays alive without polling.

Routes:
    GET  /nexus              — render nexus page with full awareness panels
    POST /nexus/chat         — send a message; returns HTMX partial
    POST /nexus/teach        — inject a fact into Timmy's live memory
    DELETE /nexus/history    — clear the nexus conversation history
    GET  /nexus/introspect   — JSON introspection snapshot (API)
    WS   /nexus/ws           — live introspection + sovereignty push

Refs: #1090 (Nexus Epic), #953 (Sovereignty Loop)
"""

import asyncio
import json
import logging
from datetime import UTC, datetime

from fastapi import APIRouter, Form, Request, WebSocket
from fastapi.responses import HTMLResponse, JSONResponse

from dashboard.templating import templates
from timmy.memory_system import (
    get_memory_stats,
    recall_personal_facts_with_ids,
    search_memories,
    store_personal_fact,
)
from timmy.nexus.introspection import nexus_introspector
from timmy.nexus.persistence import nexus_store
from timmy.nexus.sovereignty_pulse import sovereignty_pulse
from timmy.session import _clean_response, chat, reset_session

logger = logging.getLogger(__name__)

router = APIRouter(prefix="/nexus", tags=["nexus"])

_NEXUS_SESSION_ID = "nexus"
_MAX_MESSAGE_LENGTH = 10_000
_WS_PUSH_INTERVAL = 5  # seconds between WebSocket pushes

# In-memory conversation log — kept in sync with the persistent store
# so templates can render without hitting the DB on every page load.
_nexus_log: list[dict] = []

# ── Initialisation ───────────────────────────────────────────────────────────
# On module load, hydrate the in-memory log from the persistent store.
# This runs once at import time (process startup).
_HYDRATED = False


def _hydrate_log() -> None:
    """Load persisted history into the in-memory log (idempotent)."""
    global _HYDRATED
    if _HYDRATED:
        return
    try:
        rows = nexus_store.get_history(limit=200)
        _nexus_log.clear()
        for row in rows:
            _nexus_log.append(
                {
                    "role": row["role"],
                    "content": row["content"],
                    "timestamp": row["timestamp"],
                }
            )
        _HYDRATED = True
        logger.info("Nexus: hydrated %d messages from persistent store", len(_nexus_log))
    except Exception as exc:
        logger.warning("Nexus: failed to hydrate from store: %s", exc)
        _HYDRATED = True  # Don't retry repeatedly


# ── Helpers ──────────────────────────────────────────────────────────────────


def _ts() -> str:
    return datetime.now(UTC).strftime("%H:%M:%S")


def _append_log(role: str, content: str) -> None:
    """Append to both in-memory log and persistent store."""
    ts = _ts()
    _nexus_log.append({"role": role, "content": content, "timestamp": ts})
    # Bound in-memory log
    if len(_nexus_log) > 200:
        del _nexus_log[:-200]
    # Persist
    try:
        nexus_store.append(role, content, timestamp=ts)
    except Exception as exc:
        logger.warning("Nexus: persist failed: %s", exc)


# ── Page route ───────────────────────────────────────────────────────────────


@router.get("", response_class=HTMLResponse)
async def nexus_page(request: Request):
    """Render the Nexus page with full awareness panels."""
    _hydrate_log()

    stats = get_memory_stats()
    facts = recall_personal_facts_with_ids()[:8]
    introspection = nexus_introspector.snapshot(conversation_log=_nexus_log)
    pulse = sovereignty_pulse.snapshot()

    return templates.TemplateResponse(
        request,
        "nexus.html",
        {
            "page_title": "Nexus",
            "messages": list(_nexus_log),
            "stats": stats,
            "facts": facts,
            "introspection": introspection.to_dict(),
            "pulse": pulse.to_dict(),
        },
    )


# ── Chat route ───────────────────────────────────────────────────────────────


@router.post("/chat", response_class=HTMLResponse)
async def nexus_chat(request: Request, message: str = Form(...)):
    """Conversational-only chat with persistence and introspection.

    Does not invoke tool-use approval flow — pure conversation with memory
    context injected from Timmy's live memory store.
    """
    message = message.strip()
    if not message:
        return HTMLResponse("")
    if len(message) > _MAX_MESSAGE_LENGTH:
        return templates.TemplateResponse(
            request,
            "partials/nexus_message.html",
            {
                "user_message": message[:80] + "…",
                "response": None,
                "error": "Message too long (max 10 000 chars).",
                "timestamp": _ts(),
                "memory_hits": [],
                "introspection": nexus_introspector.snapshot().to_dict(),
            },
        )

    ts = _ts()

    # Fetch semantically relevant memories
    try:
        memory_hits = await asyncio.to_thread(search_memories, query=message, limit=4)
    except Exception as exc:
        logger.warning("Nexus memory search failed: %s", exc)
        memory_hits = []

    # Track memory hits for analytics
    nexus_introspector.record_memory_hits(len(memory_hits))

    # Conversational response — no tool approval flow
    response_text: str | None = None
    error_text: str | None = None
    try:
        raw = await chat(message, session_id=_NEXUS_SESSION_ID)
        response_text = _clean_response(raw)
    except Exception as exc:
        logger.error("Nexus chat error: %s", exc)
        error_text = "Timmy is unavailable right now. Check that Ollama is running."

    _append_log("user", message)
    if response_text:
        _append_log("assistant", response_text)

    # Build fresh introspection snapshot after the exchange
    introspection = nexus_introspector.snapshot(conversation_log=_nexus_log)

    return templates.TemplateResponse(
        request,
        "partials/nexus_message.html",
        {
            "user_message": message,
            "response": response_text,
            "error": error_text,
            "timestamp": ts,
            "memory_hits": memory_hits,
            "introspection": introspection.to_dict(),
        },
    )


# ── Teach route ──────────────────────────────────────────────────────────────


@router.post("/teach", response_class=HTMLResponse)
async def nexus_teach(request: Request, fact: str = Form(...)):
    """Inject a fact into Timmy's live memory from the Nexus teaching panel."""
    fact = fact.strip()
    if not fact:
        return HTMLResponse("")

    try:
        await asyncio.to_thread(store_personal_fact, fact)
        facts = await asyncio.to_thread(recall_personal_facts_with_ids)
        facts = facts[:8]
    except Exception as exc:
        logger.error("Nexus teach error: %s", exc)
        facts = []

    return templates.TemplateResponse(
        request,
        "partials/nexus_facts.html",
        {"facts": facts, "taught": fact},
    )


# ── Clear history ────────────────────────────────────────────────────────────


@router.delete("/history", response_class=HTMLResponse)
async def nexus_clear_history(request: Request):
    """Clear the Nexus conversation history (both in-memory and persistent)."""
    _nexus_log.clear()
    try:
        nexus_store.clear()
    except Exception as exc:
        logger.warning("Nexus: persistent clear failed: %s", exc)
    nexus_introspector.reset()
    reset_session(session_id=_NEXUS_SESSION_ID)

    return templates.TemplateResponse(
        request,
        "partials/nexus_message.html",
        {
            "user_message": None,
            "response": "Nexus conversation cleared.",
            "error": None,
            "timestamp": _ts(),
            "memory_hits": [],
            "introspection": nexus_introspector.snapshot().to_dict(),
        },
    )


# ── Introspection API ────────────────────────────────────────────────────────


@router.get("/introspect", response_class=JSONResponse)
async def nexus_introspect():
    """Return a JSON introspection snapshot (for API consumers)."""
    snap = nexus_introspector.snapshot(conversation_log=_nexus_log)
    pulse = sovereignty_pulse.snapshot()
    return {
        "introspection": snap.to_dict(),
        "sovereignty_pulse": pulse.to_dict(),
    }


# ── WebSocket — live Nexus push ──────────────────────────────────────────────


@router.websocket("/ws")
async def nexus_ws(websocket: WebSocket) -> None:
    """Push introspection + sovereignty pulse snapshots to the Nexus page.

    The frontend connects on page load and receives JSON updates every
    ``_WS_PUSH_INTERVAL`` seconds, keeping the cognitive state panel,
    thought stream, and sovereignty badge fresh without HTMX polling.
    """
    await websocket.accept()
    logger.info("Nexus WS connected")
    try:
        # Immediate first push
        await _push_snapshot(websocket)
        while True:
            await asyncio.sleep(_WS_PUSH_INTERVAL)
            await _push_snapshot(websocket)
    except Exception:
        logger.debug("Nexus WS disconnected")


async def _push_snapshot(ws: WebSocket) -> None:
    """Send the combined introspection + pulse payload."""
    snap = nexus_introspector.snapshot(conversation_log=_nexus_log)
    pulse = sovereignty_pulse.snapshot()
    payload = {
        "type": "nexus_state",
        "introspection": snap.to_dict(),
        "sovereignty_pulse": pulse.to_dict(),
    }
    await ws.send_text(json.dumps(payload))
