"""
cortex/integrations/openclaw_loader.py — Safe loader for OpenClaw.

NEVER crashes on import. OpenClaw calls load_cortex_plugin() at startup;
if Cortex is missing, misconfigured, or broken, this returns None and
OpenClaw boots without memory support.

Usage from OpenClaw::

    from cortex.integrations.openclaw_loader import load_cortex_plugin

    plugin = load_cortex_plugin(config)
    if plugin is None:
        logger.warning("Memory backend unavailable — running without Cortex")

Dependencies: cortex.integrations.openclaw_plugin (optional — that's the point)
"""
from __future__ import annotations

import asyncio
import logging
import os
from pathlib import Path
from typing import Any, Dict, List, Optional

logger = logging.getLogger("cortex.loader")


# ── Config Validation ───────────────────────────────────────────────────────

class ConfigValidationError(Exception):
    """Raised when OpenClaw config fails pre-flight validation."""


def validate_config(config: Dict[str, Any]) -> List[str]:
    """Validate the OpenClaw memory config before attempting to load Cortex.

    Returns:
        List of error message strings. Empty list = valid.
    """
    errors: List[str] = []

    if not isinstance(config, dict):
        return [f"Config must be a dict, got {type(config).__name__}"]

    cortex_section = config.get("cortex")
    if not cortex_section or not isinstance(cortex_section, dict):
        errors.append("Missing or invalid 'cortex' section in memory config")
        return errors

    # Required field: db_path
    db_path_raw = cortex_section.get("db_path")
    if not db_path_raw:
        errors.append("'cortex.db_path' is required")
    else:
        # Resolve and check parent directory is writable
        resolved = Path(os.path.expandvars(os.path.expanduser(db_path_raw))).resolve()
        parent = resolved.parent
        if not parent.exists():
            errors.append(f"db_path parent directory does not exist: {parent}")
        elif not os.access(str(parent), os.W_OK):
            errors.append(f"db_path parent directory is not writable: {parent}")

    # owner_id: optional but warn if missing
    if not cortex_section.get("owner_id"):
        logger.warning("No 'owner_id' in cortex config — will default to 'default'")

    return errors


# ── Main Loader ─────────────────────────────────────────────────────────────

def load_cortex_plugin(
    config: Dict[str, Any],
    *,
    health_timeout: float = 5.0,
) -> Optional[Any]:
    """Try to load and validate the Cortex plugin. Returns plugin or None.

    This is the ONLY function OpenClaw should call. It will:
    1. Validate config fields
    2. Import the plugin (catches ImportError if cortex isn't installed)
    3. Instantiate with lazy init (no DB connection in __init__)
    4. Run a health check with timeout (log-only, doesn't block boot)

    Args:
        config: The ``memory`` section from openclaw.json.
        health_timeout: Seconds to wait for health check (default 5s).

    Returns:
        An OpenClawCortexPlugin instance, or None on any failure.
    """
    # Step 1: Validate config
    try:
        errors = validate_config(config)
        if errors:
            for err in errors:
                logger.error("Config validation failed: %s", err)
            return None
    except Exception as exc:
        logger.error("Config validation crashed: %s", exc)
        return None

    # Step 2: Import (handles cortex not installed)
    try:
        from cortex.integrations.openclaw_plugin import OpenClawCortexPlugin
    except ImportError as exc:
        logger.error(
            "Cortex package not available — memory backend disabled: %s", exc
        )
        return None
    except Exception as exc:
        logger.error("Failed to import Cortex plugin: %s", exc)
        return None

    # Step 3: Instantiate (lazy — should not raise)
    try:
        plugin = OpenClawCortexPlugin(config)
    except Exception as exc:
        logger.error("Failed to instantiate Cortex plugin: %s", exc)
        return None

    # Step 4: Non-blocking health check with timeout
    try:
        loop = _get_or_create_event_loop()
        health_result = loop.run_until_complete(
            asyncio.wait_for(plugin.health(), timeout=health_timeout)
        )
        if health_result.get("ok"):
            logger.info("Cortex health check passed: %s", health_result)
        else:
            logger.warning(
                "Cortex health check reported issues (non-fatal): %s",
                health_result,
            )
    except asyncio.TimeoutError:
        logger.warning(
            "Cortex health check timed out after %.1fs (non-fatal)", health_timeout
        )
    except Exception as exc:
        logger.warning("Cortex health check failed (non-fatal): %s", exc)

    # Plugin is loaded — even if health check had issues, we return it.
    # Lazy init means actual DB work happens on first remember/recall/wake call.
    logger.info("Cortex plugin loaded successfully")
    return plugin


def _get_or_create_event_loop() -> asyncio.AbstractEventLoop:
    """Get the running event loop, or create one if none exists."""
    try:
        loop = asyncio.get_running_loop()
    except RuntimeError:
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)
    return loop
