# ClawTex — Governed Agent with Genetic Memory
# Built by DeVere Cooley / AN2B LLC · an2b.com
# BB4C — Breath Before Code

"""
Memory context builder — assembles recalled memories into prompt context.

Usage:
    ctx = MemoryContext(mandeldb_client, namespace="myagent")
    context_block = await ctx.build(user_message)   # returns formatted string
    await ctx.record_exchange(user_message, assistant_reply)
"""

from __future__ import annotations

import datetime
import logging
import os
import uuid
from pathlib import Path
from typing import Any

from .mandeldb import MandelDBClient

logger = logging.getLogger(__name__)


def _resolve_namespace(given: str | None = None) -> str:
    """
    Resolve a stable, unique namespace for this agent instance.

    Priority:
    1. Explicitly passed ``namespace`` argument
    2. ``CLAWTEX_NAME`` env var (if set and non-empty)
    3. ``.clawtex-id`` file in cwd (persisted auto-generated ID)
    4. Auto-generate ``bot:<6-char-uuid>`` and save to ``.clawtex-id``

    This ensures every new ClawTex deployment gets its own isolated
    MandelDB namespace without any manual configuration required.
    """
    if given:
        return given

    env_name = os.environ.get("CLAWTEX_NAME", "").strip()
    if env_name:
        return env_name

    id_file = Path(".clawtex-id")
    if id_file.exists():
        stored = id_file.read_text().strip()
        if stored:
            return stored

    # First run — generate and persist
    new_id = "bot:" + uuid.uuid4().hex[:6]
    try:
        id_file.write_text(new_id + "\n")
        logger.info("Auto-generated namespace '%s' → saved to .clawtex-id", new_id)
    except OSError as exc:
        logger.warning("Could not persist namespace to .clawtex-id: %s", exc)

    return new_id


class MemoryContext:
    """Wraps MandelDB to provide recall-before / ingest-after semantics."""

    def __init__(
        self,
        client: MandelDBClient | None = None,
        namespace: str | None = None,
        top_k: int = 5,
    ) -> None:
        self._client = client or MandelDBClient()
        self.namespace = _resolve_namespace(namespace)
        self.top_k = top_k

    # ------------------------------------------------------------------
    # Pre-response: recall relevant context
    # ------------------------------------------------------------------

    async def build(self, query: str) -> str:
        """
        Recall memories relevant to *query* and return a formatted context block
        ready for insertion into the system prompt.
        """
        try:
            memories = await self._client.recall(
                query=query,
                top_k=self.top_k,
                namespace=self.namespace,
            )
        except Exception as exc:  # noqa: BLE001
            logger.warning("Memory recall failed (continuing without context): %s", exc)
            return ""

        if not memories:
            return ""

        lines = ["## Relevant Memory Context\n"]
        for i, mem in enumerate(memories, 1):
            content = mem.get("content", str(mem))
            score = mem.get("score") or mem.get("similarity")
            score_str = f" [relevance: {score:.2f}]" if score is not None else ""
            ts = mem.get("metadata", {}).get("timestamp", "")
            ts_str = f" ({ts})" if ts else ""
            lines.append(f"{i}. {content.strip()}{score_str}{ts_str}")

        return "\n".join(lines)

    # ------------------------------------------------------------------
    # Post-response: ingest the exchange
    # ------------------------------------------------------------------

    async def record_exchange(
        self,
        user_message: str,
        assistant_reply: str,
        extra_metadata: dict[str, Any] | None = None,
    ) -> None:
        """
        Persist the user↔assistant exchange to MandelDB for future recall.
        """
        now = datetime.datetime.utcnow().isoformat() + "Z"
        content = f"User: {user_message}\nAssistant: {assistant_reply}"
        metadata: dict[str, Any] = {
            "timestamp": now,
            "agent": self.namespace,
            "type": "exchange",
        }
        if extra_metadata:
            metadata.update(extra_metadata)

        try:
            await self._client.ingest(
                content=content,
                metadata=metadata,
                namespace=self.namespace,
            )
        except Exception as exc:  # noqa: BLE001
            logger.warning("Memory ingest failed (non-fatal): %s", exc)

    async def ingest_fact(
        self, content: str, metadata: dict[str, Any] | None = None
    ) -> None:
        """Store an arbitrary fact or note in memory."""
        base: dict[str, Any] = {
            "timestamp": datetime.datetime.utcnow().isoformat() + "Z",
            "agent": self.namespace,
            "type": "fact",
        }
        if metadata:
            base.update(metadata)
        try:
            await self._client.ingest(
                content=content, metadata=base, namespace=self.namespace
            )
        except Exception as exc:  # noqa: BLE001
            logger.warning("Fact ingest failed (non-fatal): %s", exc)

    async def trigger_dream(self) -> dict[str, Any]:
        """Trigger MandelDB memory consolidation for this agent's namespace."""
        return await self._client.dream(self.namespace)
