"""
cortex/integrations/openclaw_plugin.py — OpenClaw memory backend bridge.

Adapts Cortex v3's CortexPlugin API to OpenClaw's memory interface:
    - remember(conversation, metadata) → CortexPlugin.remember()
    - recall(query, options)           → CortexPlugin.retrieve() + format
    - on_session_start(session_id)     → CortexPlugin.wake()
    - on_session_end(session_id)       → CortexPlugin.sleep()
    - health()                         → CortexPlugin.health()

Design principles:
    - THIN bridge — adapt interfaces only, no business logic
    - Cortex handles extraction, retrieval, cornerstones, etc.
    - Graceful degradation: Cortex failure → empty context, never crash OpenClaw
    - Session mapping: OpenClaw session_id → Cortex wake()/sleep()

Dependencies: cortex.api.plugin.CortexPlugin, cortex.integrations.openclaw_config
Used by: OpenClaw memory plugin system (external)
"""
from __future__ import annotations

import logging
from typing import Any, Dict, List, Optional

from cortex.api.plugin import CortexPlugin
from cortex.integrations.openclaw_config import build_cortex_config

logger = logging.getLogger(__name__)


class OpenClawCortexPlugin:
    """Adapts Cortex v3 to OpenClaw's memory plugin interface.

    Usage (from OpenClaw's perspective)::

        from cortex.integrations.openclaw_plugin import OpenClawCortexPlugin

        plugin = OpenClawCortexPlugin(config={
            "backend": "cortex",
            "cortex": {
                "db_path": "~/.openclaw/cortex.db",
                "owner_id": "eva",
                "cornerstone_injection": True,
            }
        })

        await plugin.on_session_start("session-123")
        await plugin.remember(conversation, metadata)
        context = await plugin.recall("What does the user prefer?")
        await plugin.on_session_end("session-123")
    """

    def __init__(self, config: Dict[str, Any]) -> None:
        """Initialize from OpenClaw memory config section.

        Uses **lazy initialization**: no DB connections are made during __init__.
        The CortexPlugin is created on the first call to remember/recall/wake/health.
        This ensures OpenClaw never crashes on startup due to Cortex issues.

        Args:
            config: The ``memory`` section from ``openclaw.json``. Expected shape::

                {
                    "backend": "cortex",
                    "cortex": {
                        "db_path": "~/.openclaw/cortex.db",
                        "config_path": "~/.openclaw/cortex.toml",
                        "owner_id": "eva",
                        "auto_sleep_on_session_end": true,
                        "cornerstone_injection": true
                    }
                }
        """
        self._raw_config = config
        self._cortex_config, self._extra_opts = build_cortex_config(config)
        self._auto_sleep = self._extra_opts.get("auto_sleep_on_session_end", True)
        self._cornerstone_injection = self._extra_opts.get(
            "cornerstone_injection", True
        )

        # Lazy initialization — plugin is NOT created until first use
        self._plugin: Optional[CortexPlugin] = None
        self._init_failed: bool = False
        self._init_error: Optional[str] = None

        self._current_session: Optional[str] = None
        logger.info(
            "OpenClawCortexPlugin created (lazy init, owner=%s, db=%s)",
            self._cortex_config.owner_id,
            self._cortex_config.storage_db_path,
        )

    def _ensure_plugin(self) -> Optional[CortexPlugin]:
        """Lazily initialize the CortexPlugin on first use.

        Returns:
            The CortexPlugin instance, or None if initialization failed.
        """
        if self._plugin is not None:
            return self._plugin

        if self._init_failed:
            # Don't retry after a failure — return None until restart
            return None

        try:
            self._plugin = CortexPlugin(
                config_path=self._extra_opts.get("config_path"),
                db_path=self._cortex_config.storage_db_path,
            )
            self._plugin.config.owner_id = self._cortex_config.owner_id
            logger.info(
                "CortexPlugin initialized on first use (owner=%s, db=%s)",
                self._cortex_config.owner_id,
                self._cortex_config.storage_db_path,
            )
            return self._plugin
        except Exception as exc:
            self._init_failed = True
            self._init_error = str(exc)
            logger.error(
                "CortexPlugin lazy init failed (will not retry): %s",
                exc,
                exc_info=True,
            )
            return None

    # =========================================================================
    # WRITE — remember
    # =========================================================================

    async def remember(
        self,
        conversation: List[Dict[str, Any]],
        metadata: Optional[Dict[str, Any]] = None,
    ) -> Dict[str, Any]:
        """Store memories from a conversation turn.

        Called by OpenClaw after each conversation turn (auto-capture hook).

        Args:
            conversation: List of message dicts, e.g.::
                [{"role": "user", "content": "..."}, {"role": "assistant", "content": "..."}]
            metadata: Optional metadata dict (session_id, timestamp, etc.)

        Returns:
            Dict with ``ok``, ``claims_extracted``, ``claims_stored``, ``errors``.
        """
        # Filter out noisy sessions (subagent, cron, isolated)
        session_id = (metadata or {}).get("session_id", "") or ""
        if any(noise in session_id for noise in (":subagent:", ":cron:", ":isolated:")):
            return {"ok": True, "skipped": True, "reason": "noisy_session"}

        try:
            plugin = self._ensure_plugin()
            if plugin is None:
                logger.warning("remember() skipped — plugin not initialized")
                return {
                    "ok": False,
                    "claims_extracted": 0,
                    "claims_stored": 0,
                    "entities_resolved": 0,
                    "errors": [self._init_error or "Plugin not initialized"],
                }

            session_id = (
                (metadata or {}).get("session_id")
                or self._current_session
            )

            result = await plugin.remember(
                conversation=conversation,
                session_id=session_id,
            )

            return {
                "ok": result.ok,
                "claims_extracted": result.claims_extracted,
                "claims_stored": result.claims_stored,
                "entities_resolved": result.entities_resolved,
                "errors": result.errors,
                "warnings": getattr(result, "warnings", []),
                "zero_claim_warning": getattr(result, "zero_claim_warning", False),
            }
        except Exception as exc:
            logger.error("remember() failed: %s", exc, exc_info=True)
            return {
                "ok": False,
                "claims_extracted": 0,
                "claims_stored": 0,
                "entities_resolved": 0,
                "errors": [str(exc)],
            }

    # =========================================================================
    # READ — recall
    # =========================================================================

    async def recall(
        self,
        query: str,
        options: Optional[Dict[str, Any]] = None,
    ) -> str:
        """Retrieve relevant memories for context injection.

        Called by OpenClaw before each turn (auto-recall hook).

        Args:
            query: The user's message or a search query.
            options: Optional dict with:
                - token_budget (int): max tokens for context
                - top_k (int): max items to return

        Returns:
            Formatted context string for system prompt injection.
            Returns empty string on error (graceful degradation).
        """
        try:
            plugin = self._ensure_plugin()
            if plugin is None:
                logger.warning("recall() skipped — plugin not initialized")
                return ""

            opts = options or {}
            token_budget = opts.get("token_budget")

            result = await plugin.retrieve(
                query=query,
                token_budget=token_budget,
            )

            if result is None:
                return ""

            return self._format_recall_response(result)

        except Exception as exc:
            logger.error("recall() failed: %s", exc, exc_info=True)
            return ""

    def _format_recall_response(self, retrieval_result: Any) -> str:
        """Format a RetrievalResult into a context string for prompt injection.

        Groups items by type (cornerstones first when enabled), then formats
        each as a labeled block.

        Args:
            retrieval_result: A RetrievalResult from CortexPlugin.retrieve().

        Returns:
            Formatted string ready for system prompt injection.
        """
        if not retrieval_result or not retrieval_result.items:
            return ""

        parts: List[str] = []

        # Separate cornerstones from regular items
        cornerstones: List[Any] = []
        memories: List[Any] = []

        for item in retrieval_result.items:
            if item.item_type == "cornerstone":
                cornerstones.append(item)
            else:
                memories.append(item)

        # Cornerstones first (if injection enabled)
        if self._cornerstone_injection and cornerstones:
            parts.append("## Core Identity & Preferences")
            for item in cornerstones:
                parts.append(f"- {item.content}")
            parts.append("")

        # Regular memories
        if memories:
            parts.append("## Relevant Memories")
            for item in memories:
                parts.append(f"- [{item.item_type}] {item.content}")
            parts.append("")

        return "\n".join(parts).strip()

    # =========================================================================
    # SESSION LIFECYCLE
    # =========================================================================

    async def on_session_start(self, session_id: str) -> Dict[str, Any]:
        """Called when an OpenClaw session starts.

        Maps to CortexPlugin.wake() — loads cornerstones, initializes session.

        Args:
            session_id: The OpenClaw session identifier.

        Returns:
            Dict with ``session_id``, ``cornerstones_loaded``, ``errors``.
        """
        try:
            self._current_session = session_id
            plugin = self._ensure_plugin()
            if plugin is None:
                logger.warning("on_session_start() skipped — plugin not initialized")
                return {
                    "session_id": session_id,
                    "cornerstones_loaded": 0,
                    "errors": [self._init_error or "Plugin not initialized"],
                }
            report = await plugin.wake(session_id=session_id)

            return {
                "session_id": report.session_id,
                "cornerstones_loaded": report.cornerstones_loaded,
                "errors": report.errors,
            }
        except Exception as exc:
            logger.error("on_session_start() failed: %s", exc, exc_info=True)
            self._current_session = session_id  # still track it
            return {
                "session_id": session_id,
                "cornerstones_loaded": 0,
                "errors": [str(exc)],
            }

    async def on_session_end(self, session_id: str) -> Dict[str, Any]:
        """Called when an OpenClaw session ends.

        Maps to CortexPlugin.sleep() — closes session, runs cleanup.

        Args:
            session_id: The OpenClaw session identifier.

        Returns:
            Dict with ``session_id``, ``errors``.
        """
        if not self._auto_sleep:
            self._current_session = None
            return {"session_id": session_id, "errors": []}

        try:
            plugin = self._ensure_plugin()
            if plugin is None:
                self._current_session = None
                return {"session_id": session_id, "errors": []}
            report = await plugin.sleep(session_id=session_id)
            self._current_session = None

            return {
                "session_id": report.session_id,
                "errors": report.errors,
            }
        except Exception as exc:
            logger.error("on_session_end() failed: %s", exc, exc_info=True)
            self._current_session = None
            return {
                "session_id": session_id,
                "errors": [str(exc)],
            }

    # =========================================================================
    # HEALTH
    # =========================================================================

    async def health(self) -> Dict[str, Any]:
        """Check if the Cortex memory system is working.

        Returns:
            Dict with ``ok`` (bool), ``modules`` (dict), ``errors`` (list).
        """
        try:
            plugin = self._ensure_plugin()
            if plugin is None:
                return {
                    "ok": False,
                    "backend": "cortex",
                    "storage": "not_initialized",
                    "modules": {},
                    "errors": [self._init_error or "Plugin not initialized"],
                }
            report = await plugin.health()
            return {
                "ok": report.ok,
                "backend": "cortex",
                "storage": report.storage,
                "modules": report.modules,
                "errors": report.errors,
            }
        except Exception as exc:
            logger.error("health() failed: %s", exc, exc_info=True)
            return {
                "ok": False,
                "backend": "cortex",
                "storage": "unknown",
                "modules": {},
                "errors": [str(exc)],
            }
