"""Hermes memory provider for Memory System API.

Memory System API wraps OpenViking session memory and EverOS user profile
memory behind one small HTTP surface. This plugin keeps Hermes integration
thin: completed turns are written to the API, session end triggers commit,
and tools expose search/profile/explicit remember operations.
"""

from __future__ import annotations

import json
import logging
import os
import threading
import time
from pathlib import Path
from typing import Any, Dict, List, Optional
from urllib.parse import urlencode

from agent.memory_provider import MemoryProvider
from tools.registry import tool_error

logger = logging.getLogger(__name__)

_DEFAULT_ENDPOINT = "http://127.0.0.1:1934"
_DEFAULT_TIMEOUT = 180.0
_CONFIG_KEYS = {
    "MEMORY_SYSTEM_ENDPOINT",
    "MEMORY_SYSTEM_API_KEY",
    "MEMORY_SYSTEM_USER_ID",
    "MEMORY_SYSTEM_USER_KEY",
    "MEMORY_SYSTEM_SEARCH_USE_LLM",
    "MEMORY_SYSTEM_COMMIT_EVERY_TURNS",
    "MEMORY_SYSTEM_COMMIT_INTERVAL_SECONDS",
    "MEMORY_SYSTEM_TIMEOUT_SECONDS",
}


def _get_httpx():
    try:
        import httpx

        return httpx
    except ImportError:
        return None


def _env_bool(name: str, default: bool = False) -> bool:
    return _bool_value(_memory_system_config().get(name), default)


def _bool_value(value: Optional[str], default: bool = False) -> bool:
    if value is None:
        return default
    return value.strip().lower() in {"1", "true", "yes", "on"}


def _env_int(name: str, default: int) -> int:
    return _int_value(_memory_system_config().get(name), default)


def _int_value(value: Optional[str], default: int) -> int:
    if value is None or value.strip() == "":
        return default
    try:
        return int(value)
    except ValueError:
        return default


def _env_float(name: str, default: float) -> float:
    return _float_value(_memory_system_config().get(name), default)


def _float_value(value: Optional[str], default: float) -> float:
    if value is None or value.strip() == "":
        return default
    try:
        return float(value)
    except ValueError:
        return default


def _parse_env_file(path: Path) -> Dict[str, str]:
    values: Dict[str, str] = {}
    try:
        lines = path.read_text(encoding="utf-8").splitlines()
    except OSError:
        return values

    for line in lines:
        stripped = line.strip()
        if not stripped or stripped.startswith("#"):
            continue
        if stripped.startswith("export "):
            stripped = stripped[len("export ") :].strip()
        if "=" not in stripped:
            continue
        key, value = stripped.split("=", 1)
        key = key.strip()
        if key not in _CONFIG_KEYS:
            continue
        value = value.strip().strip('"').strip("'")
        values[key] = value
    return values


def _memory_system_config(hermes_home: str = "") -> Dict[str, str]:
    candidates: List[Path] = []
    candidates.extend(
        [
            Path.cwd() / ".env",
            Path.cwd() / "memory_system.env",
            Path.home() / ".hermes" / ".env",
            Path.home() / ".hermes" / "memory_system.env",
        ]
    )
    if hermes_home:
        candidates.append(Path(hermes_home).expanduser() / ".env")
        candidates.append(Path(hermes_home).expanduser() / "memory_system.env")
    env_hermes_home = os.environ.get("HERMES_HOME", "")
    if env_hermes_home:
        candidates.append(Path(env_hermes_home).expanduser() / ".env")
        candidates.append(Path(env_hermes_home).expanduser() / "memory_system.env")
    explicit_file = os.environ.get("MEMORY_SYSTEM_ENV_FILE", "")
    if explicit_file:
        candidates.append(Path(explicit_file).expanduser())

    config: Dict[str, str] = {}
    seen: set[Path] = set()
    for path in candidates:
        resolved = path.expanduser()
        if resolved in seen:
            continue
        seen.add(resolved)
        config.update(_parse_env_file(resolved))

    for key in _CONFIG_KEYS:
        if key in os.environ:
            config[key] = os.environ[key]
    return config


class _MemorySystemClient:
    """Small sync HTTP client for Memory System API."""

    def __init__(self, endpoint: str, api_key: str = "", timeout: float = _DEFAULT_TIMEOUT):
        self._endpoint = endpoint.rstrip("/")
        self._api_key = api_key
        self._timeout = timeout
        self._httpx = _get_httpx()
        if self._httpx is None:
            raise ImportError("httpx is required for memory_system: pip install httpx")

    def _headers(self) -> Dict[str, str]:
        headers = {"Content-Type": "application/json"}
        if self._api_key:
            headers["X-API-Key"] = self._api_key
        return headers

    def _url(self, path: str) -> str:
        return f"{self._endpoint}{path}"

    def get(self, path: str) -> Dict[str, Any]:
        response = self._httpx.get(self._url(path), headers=self._headers(), timeout=self._timeout)
        response.raise_for_status()
        return response.json()

    def delete(self, path: str) -> Dict[str, Any]:
        response = self._httpx.delete(self._url(path), headers=self._headers(), timeout=self._timeout)
        response.raise_for_status()
        return response.json()

    def post(self, path: str, payload: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
        response = self._httpx.post(
            self._url(path),
            json=payload or {},
            headers=self._headers(),
            timeout=self._timeout,
        )
        response.raise_for_status()
        return response.json()

    def health(self) -> bool:
        try:
            response = self._httpx.get(
                self._url("/memory-system/health"),
                headers=self._headers(),
                timeout=3.0,
            )
            return response.status_code == 200
        except Exception:
            return False


SEARCH_SCHEMA = {
    "name": "memory_system_search",
    "description": (
        "Search persistent memory through Memory System API. "
        "By default this uses fast hybrid search; set use_llm=true for agentic search."
    ),
    "parameters": {
        "type": "object",
        "properties": {
            "query": {"type": "string", "description": "Search query."},
            "use_llm": {
                "type": "boolean",
                "description": "Use agentic LLM search instead of hybrid search.",
            },
            "limit": {"type": "integer", "description": "Maximum results, default 10."},
            "session_id": {
                "type": "string",
                "description": "Optional session override. Defaults to the active Hermes session.",
            },
        },
        "required": ["query"],
    },
}

PROFILE_SCHEMA = {
    "name": "memory_system_profile",
    "description": "Read the current user's profile memory from Memory System API.",
    "parameters": {
        "type": "object",
        "properties": {
            "user_id": {
                "type": "string",
                "description": "Optional user override. Defaults to the active Hermes user.",
            },
            "user_key": {
                "type": "string",
                "description": "Required only when user_id overrides the active Hermes user.",
            },
        },
    },
}

REMEMBER_SCHEMA = {
    "name": "memory_system_remember",
    "description": (
        "Store an important memory through Memory System API and commit the active session. "
        "Use this when information should be remembered beyond the current conversation."
    ),
    "parameters": {
        "type": "object",
        "properties": {
            "content": {"type": "string", "description": "Memory text to store."},
            "session_id": {
                "type": "string",
                "description": "Optional session override. Defaults to the active Hermes session.",
            },
        },
        "required": ["content"],
    },
}

MEMORY_LIST_SCHEMA = {
    "name": "memory_system_memory_list",
    "description": "List OpenViking memory URIs through Memory System API.",
    "parameters": {
        "type": "object",
        "properties": {
            "uri": {
                "type": "string",
                "description": "Memory root URI. Defaults to viking://user/memories.",
            },
            "recursive": {
                "type": "boolean",
                "description": "Whether to list recursively. Defaults to true.",
            },
        },
    },
}

MEMORY_READ_SCHEMA = {
    "name": "memory_system_memory_read",
    "description": "Read one OpenViking memory URI through Memory System API.",
    "parameters": {
        "type": "object",
        "properties": {
            "uri": {"type": "string", "description": "Memory URI to read."},
        },
        "required": ["uri"],
    },
}

MEMORY_WRITE_SCHEMA = {
    "name": "memory_system_memory_write",
    "description": "Create, replace, or append an OpenViking memory URI through Memory System API.",
    "parameters": {
        "type": "object",
        "properties": {
            "uri": {"type": "string", "description": "Target memory URI."},
            "content": {"type": "string", "description": "Memory content to write."},
            "mode": {
                "type": "string",
                "enum": ["create", "replace", "append"],
                "description": "Write mode. Use replace to modify an existing memory.",
            },
            "wait": {
                "type": "boolean",
                "description": "Wait for Memory System processing/indexing. Defaults to true.",
            },
        },
        "required": ["uri", "content"],
    },
}

MEMORY_DELETE_SCHEMA = {
    "name": "memory_system_memory_delete",
    "description": "Delete one OpenViking memory URI through Memory System API.",
    "parameters": {
        "type": "object",
        "properties": {
            "uri": {"type": "string", "description": "Memory URI to delete."},
            "recursive": {
                "type": "boolean",
                "description": "Delete recursively. Defaults to false; use true only for directory-like memories.",
            },
        },
        "required": ["uri"],
    },
}


class MemorySystemMemoryProvider(MemoryProvider):
    """Hermes MemoryProvider backed by Memory System API."""

    @property
    def name(self) -> str:
        return "memory_system"

    def __init__(self) -> None:
        config = _memory_system_config()
        self._endpoint = config.get("MEMORY_SYSTEM_ENDPOINT", _DEFAULT_ENDPOINT)
        self._api_key = config.get("MEMORY_SYSTEM_API_KEY", "")
        self._user_id = config.get("MEMORY_SYSTEM_USER_ID", "default")
        self._user_key = config.get("MEMORY_SYSTEM_USER_KEY", "")
        self._session_id = ""
        self._client: Optional[_MemorySystemClient] = None
        self._sync_thread: Optional[threading.Thread] = None
        self._prefetch_thread: Optional[threading.Thread] = None
        self._prefetch_lock = threading.Lock()
        self._prefetch_result = ""
        self._turn_count = 0
        self._last_commit_turn = 0
        self._last_commit_time = time.monotonic()
        self._commit_every_turns = _env_int("MEMORY_SYSTEM_COMMIT_EVERY_TURNS", 5)
        self._commit_interval_seconds = _env_int("MEMORY_SYSTEM_COMMIT_INTERVAL_SECONDS", 300)
        self._timeout = _env_float("MEMORY_SYSTEM_TIMEOUT_SECONDS", _DEFAULT_TIMEOUT)
        self._default_use_llm = _env_bool("MEMORY_SYSTEM_SEARCH_USE_LLM", False)

    def is_available(self) -> bool:
        return bool(_memory_system_config().get("MEMORY_SYSTEM_ENDPOINT")) and _get_httpx() is not None

    def get_config_schema(self) -> List[Dict[str, Any]]:
        return [
            {
                "key": "endpoint",
                "description": "Memory System API endpoint.",
                "required": True,
                "default": _DEFAULT_ENDPOINT,
                "env_var": "MEMORY_SYSTEM_ENDPOINT",
            },
            {
                "key": "api_key",
                "description": "Memory System API key, if server.api_key is configured.",
                "secret": True,
                "required": False,
                "env_var": "MEMORY_SYSTEM_API_KEY",
            },
            {
                "key": "user_id",
                "description": "Default Memory System user id for this Hermes profile.",
                "required": False,
                "default": "default",
                "env_var": "MEMORY_SYSTEM_USER_ID",
            },
            {
                "key": "user_key",
                "description": (
                    "Memory System user key returned by /memory-system/users. "
                    "If omitted, the plugin creates or looks up the configured user on initialization."
                ),
                "secret": True,
                "required": False,
                "env_var": "MEMORY_SYSTEM_USER_KEY",
            },
            {
                "key": "commit_every_turns",
                "description": "Commit after this many new turns. Set 0 to disable turn-based commits.",
                "required": False,
                "default": 5,
                "env_var": "MEMORY_SYSTEM_COMMIT_EVERY_TURNS",
            },
            {
                "key": "commit_interval_seconds",
                "description": "Commit after this many seconds if new turns exist. Set 0 to disable time-based commits.",
                "required": False,
                "default": 300,
                "env_var": "MEMORY_SYSTEM_COMMIT_INTERVAL_SECONDS",
            },
            {
                "key": "timeout_seconds",
                "description": "HTTP timeout for Memory System API requests. Commit may wait for EverOS LLM extraction.",
                "required": False,
                "default": _DEFAULT_TIMEOUT,
                "env_var": "MEMORY_SYSTEM_TIMEOUT_SECONDS",
            },
        ]

    def initialize(self, session_id: str, **kwargs) -> None:
        config = _memory_system_config(str(kwargs.get("hermes_home") or ""))
        self._endpoint = config.get("MEMORY_SYSTEM_ENDPOINT", _DEFAULT_ENDPOINT)
        self._api_key = config.get("MEMORY_SYSTEM_API_KEY", "")
        self._user_id = (
            config.get("MEMORY_SYSTEM_USER_ID")
            or kwargs.get("user_id")
            or kwargs.get("agent_identity")
            or "default"
        )
        self._user_key = config.get("MEMORY_SYSTEM_USER_KEY") or kwargs.get("user_key") or ""
        self._session_id = session_id
        self._default_use_llm = _bool_value(config.get("MEMORY_SYSTEM_SEARCH_USE_LLM"), False)
        self._commit_every_turns = _int_value(config.get("MEMORY_SYSTEM_COMMIT_EVERY_TURNS"), 5)
        self._commit_interval_seconds = _int_value(
            config.get("MEMORY_SYSTEM_COMMIT_INTERVAL_SECONDS"), 300
        )
        self._timeout = _float_value(config.get("MEMORY_SYSTEM_TIMEOUT_SECONDS"), _DEFAULT_TIMEOUT)
        self._last_commit_turn = 0
        self._last_commit_time = time.monotonic()

        try:
            client = _MemorySystemClient(self._endpoint, self._api_key, self._timeout)
            if not client.health():
                logger.warning("Memory System API health check failed: %s", self._endpoint)
                return
            self._client = client
            self._ensure_user_key()
        except Exception as exc:
            logger.warning("Memory System API initialization failed: %s", exc)
            self._client = None

    def system_prompt_block(self) -> str:
        if not self._client:
            return ""
        return (
            "# Memory System\n"
            "Persistent memory is active. Use memory_system_search for recall, "
            "memory_system_profile for user profile, and memory_system_remember "
            "for important information that should be stored. Use memory_system_memory_* "
            "tools only when you need to list, read, edit, or delete specific memory URIs."
        )

    def prefetch(self, query: str, *, session_id: str = "") -> str:
        if self._prefetch_thread and self._prefetch_thread.is_alive():
            self._prefetch_thread.join(timeout=3.0)
        with self._prefetch_lock:
            result = self._prefetch_result
            self._prefetch_result = ""
        if not result:
            return ""
        return f"## Memory System Context\n{result}"

    def queue_prefetch(self, query: str, *, session_id: str = "") -> None:
        if not self._client or not query:
            return

        def _run() -> None:
            try:
                response = self._client.post(
                    "/memory-system/search",
                    {
                        "user_id": self._user_id,
                        "user_key": self._user_key,
                        "session_id": session_id or self._session_id,
                        "query": query,
                        "use_llm": self._default_use_llm,
                        "limit": 5,
                    },
                )
                formatted = self._format_items(response.get("items", []), limit=5)
                if formatted:
                    with self._prefetch_lock:
                        self._prefetch_result = formatted
            except Exception as exc:
                logger.debug("Memory System prefetch failed: %s", exc)

        self._prefetch_thread = threading.Thread(
            target=_run, daemon=True, name="memory-system-prefetch"
        )
        self._prefetch_thread.start()

    def sync_turn(self, user_content: str, assistant_content: str, *, session_id: str = "") -> None:
        if not self._client:
            return

        self._turn_count += 1

        def _sync() -> None:
            try:
                payload = self._message_payload(
                    session_id=session_id or self._session_id,
                    user_message=user_content,
                    assistant_message=assistant_content,
                )
                self._client.post("/memory-system/messages", payload)
                if self._should_commit_now():
                    self._commit_session(session_id or self._session_id)
            except Exception as exc:
                logger.debug("Memory System sync_turn failed: %s", exc)

        if self._sync_thread and self._sync_thread.is_alive():
            self._sync_thread.join(timeout=5.0)

        self._sync_thread = threading.Thread(target=_sync, daemon=True, name="memory-system-sync")
        self._sync_thread.start()

    def on_session_end(self, messages: List[Dict[str, Any]]) -> None:
        if not self._client:
            return
        if self._sync_thread and self._sync_thread.is_alive():
            self._sync_thread.join(timeout=10.0)
        if self._turn_count == 0 or self._last_commit_turn >= self._turn_count:
            return
        try:
            self._commit_session(self._session_id)
        except Exception as exc:
            logger.warning("Memory System session commit failed: %s", exc)

    def on_memory_write(self, action: str, target: str, content: str) -> None:
        if action != "add" or not content:
            return
        self._remember(content, session_id=self._session_id, commit=False)

    def get_tool_schemas(self) -> List[Dict[str, Any]]:
        return [
            SEARCH_SCHEMA,
            PROFILE_SCHEMA,
            REMEMBER_SCHEMA,
            MEMORY_LIST_SCHEMA,
            MEMORY_READ_SCHEMA,
            MEMORY_WRITE_SCHEMA,
            MEMORY_DELETE_SCHEMA,
        ]

    def handle_tool_call(self, tool_name: str, args: Dict[str, Any], **kwargs) -> str:
        if not self._client:
            return tool_error("Memory System API is not connected")
        try:
            if tool_name == "memory_system_search":
                return self._tool_search(args)
            if tool_name == "memory_system_profile":
                return self._tool_profile(args)
            if tool_name == "memory_system_remember":
                return self._tool_remember(args)
            if tool_name == "memory_system_memory_list":
                return self._tool_memory_list(args)
            if tool_name == "memory_system_memory_read":
                return self._tool_memory_read(args)
            if tool_name == "memory_system_memory_write":
                return self._tool_memory_write(args)
            if tool_name == "memory_system_memory_delete":
                return self._tool_memory_delete(args)
            return tool_error(f"Unknown tool: {tool_name}")
        except Exception as exc:
            return tool_error(str(exc))

    def shutdown(self) -> None:
        for thread in (self._sync_thread, self._prefetch_thread):
            if thread and thread.is_alive():
                thread.join(timeout=5.0)

    def _message_payload(
        self,
        *,
        session_id: str,
        user_message: str = "",
        assistant_message: str = "",
    ) -> Dict[str, Any]:
        payload: Dict[str, Any] = {
            "user_id": self._user_id,
            "user_key": self._user_key,
            "session_id": session_id,
            "metadata": {"source": "hermes", "provider": "memory_system"},
        }
        if user_message:
            payload["user_message"] = user_message[:4000]
        if assistant_message:
            payload["assistant_message"] = assistant_message[:4000]
        return payload

    def _format_items(self, items: List[Dict[str, Any]], *, limit: int) -> str:
        parts = []
        for item in items[:limit]:
            text = self._memory_text(item)
            if not text:
                continue
            source = item.get("source_backend") or item.get("source") or item.get("backend") or "memory"
            score = item.get("score")
            prefix = f"[{source}]"
            if isinstance(score, (int, float)):
                prefix = f"{prefix} {score:.2f}"
            parts.append(f"- {prefix} {text}")
        return "\n".join(parts)

    def _memory_text(self, item: Dict[str, Any]) -> str:
        for key in (
            "memory",
            "content",
            "text",
            "summary",
            "abstract",
            "fact",
            "value",
        ):
            value = item.get(key)
            if isinstance(value, str) and value.strip():
                return value.strip()
        for key in ("memory", "content", "text", "summary"):
            value = item.get("data", {}).get(key) if isinstance(item.get("data"), dict) else None
            if isinstance(value, str) and value.strip():
                return value.strip()
        return ""

    def _compact_search_response(self, response: Dict[str, Any], *, limit: int) -> Dict[str, Any]:
        compact_items = []
        for item in response.get("items", [])[:limit]:
            if not isinstance(item, dict):
                continue
            text = self._memory_text(item)
            if not text:
                continue
            compact: Dict[str, Any] = {
                "source_backend": item.get("source_backend") or item.get("source") or item.get("backend") or "memory",
                "text": text[:1200],
            }
            memory_type = item.get("memory_type") or item.get("type") or item.get("category")
            if memory_type:
                compact["memory_type"] = memory_type
            score = item.get("score")
            if isinstance(score, (int, float)):
                compact["score"] = score
            uri = item.get("uri")
            if isinstance(uri, str) and uri.startswith("viking://"):
                compact["uri"] = uri
            compact_items.append(compact)

        result: Dict[str, Any] = {
            "status": response.get("status", "success"),
            "items": compact_items,
        }
        if not compact_items:
            result["message"] = "No memory items found."
        return result

    def _should_commit_now(self) -> bool:
        if self._last_commit_turn >= self._turn_count:
            return False
        new_turns = self._turn_count - self._last_commit_turn
        if self._commit_every_turns > 0 and new_turns >= self._commit_every_turns:
            return True
        elapsed = time.monotonic() - self._last_commit_time
        return self._commit_interval_seconds > 0 and elapsed >= self._commit_interval_seconds

    def _commit_session(self, session_id: str) -> Dict[str, Any]:
        response = self._client.post(
            f"/memory-system/sessions/{session_id}/commit",
            self._user_payload(),
        )
        if response.get("status") == "success":
            self._last_commit_turn = self._turn_count
            self._last_commit_time = time.monotonic()
        else:
            logger.warning("Memory System commit did not fully succeed: %s", response)
        return response

    def _tool_search(self, args: Dict[str, Any]) -> str:
        query = str(args.get("query", "")).strip()
        if not query:
            return tool_error("query is required")
        limit = int(args.get("limit") or 10)
        limit = max(1, min(limit, 100))
        payload = {
            "user_id": self._user_id,
            "user_key": self._user_key,
            "session_id": args.get("session_id") or self._session_id,
            "query": query,
            "use_llm": bool(args.get("use_llm", self._default_use_llm)),
            "limit": limit,
        }
        response = self._client.post("/memory-system/search", payload)
        return json.dumps(self._compact_search_response(response, limit=limit), ensure_ascii=False)

    def _tool_profile(self, args: Dict[str, Any]) -> str:
        user_id = str(args.get("user_id") or self._user_id).strip()
        if not user_id:
            return tool_error("user_id is required")
        user_key = (
            self._user_key
            if user_id == self._user_id
            else str(args.get("user_key") or "").strip()
        )
        if not user_key:
            return tool_error("user_key is required for profile reads")
        path = f"/memory-system/users/{user_id}/profile?{urlencode({'user_key': user_key})}"
        return json.dumps(
            self._client.get(path),
            ensure_ascii=False,
        )

    def _tool_remember(self, args: Dict[str, Any]) -> str:
        content = str(args.get("content", "")).strip()
        if not content:
            return tool_error("content is required")
        session_id = str(args.get("session_id") or self._session_id).strip()
        return json.dumps(self._remember(content, session_id=session_id, commit=True), ensure_ascii=False)

    def _remember(self, content: str, *, session_id: str, commit: bool) -> Dict[str, Any]:
        if not self._client:
            return {"error": "Memory System API is not connected"}
        response = self._client.post(
            "/memory-system/messages",
            self._message_payload(session_id=session_id, user_message=content),
        )
        if commit:
            commit_response = self._client.post(
                f"/memory-system/sessions/{session_id}/commit",
                self._user_payload(),
            )
            return {"status": response.get("status"), "write": response, "commit": commit_response}
        return response

    def _ensure_user_key(self) -> None:
        if self._user_key:
            return
        if not self._client:
            return
        response = self._client.post("/memory-system/users", {"user_id": self._user_id})
        result = (
            response.get("account", {}).get("result", {})
            if isinstance(response.get("account"), dict)
            else {}
        )
        user_key = result.get("user_key")
        if not user_key:
            raise ValueError("Memory System user creation response missing account.result.user_key")
        self._user_key = str(user_key)

    def _user_payload(self) -> Dict[str, str]:
        if not self._user_key:
            raise ValueError("Memory System user_key is required")
        return {"user_id": self._user_id, "user_key": self._user_key}

    def _query_path(self, path: str, params: Dict[str, Any]) -> str:
        return f"{path}?{urlencode(params)}"

    def _tool_memory_list(self, args: Dict[str, Any]) -> str:
        uri = str(args.get("uri") or "viking://user/memories").strip()
        recursive = self._arg_bool(args.get("recursive"), True)
        path = self._query_path(
            "/memory-system/memories",
            {
                "user_id": self._user_id,
                "user_key": self._user_key,
                "uri": uri,
                "recursive": str(recursive).lower(),
            },
        )
        return json.dumps(self._client.get(path), ensure_ascii=False)

    def _tool_memory_read(self, args: Dict[str, Any]) -> str:
        uri = str(args.get("uri", "")).strip()
        if not uri:
            return tool_error("uri is required")
        path = self._query_path(
            "/memory-system/memories/content",
            {"user_id": self._user_id, "user_key": self._user_key, "uri": uri},
        )
        return json.dumps(self._client.get(path), ensure_ascii=False)

    def _tool_memory_write(self, args: Dict[str, Any]) -> str:
        uri = str(args.get("uri", "")).strip()
        content = str(args.get("content", ""))
        if not uri:
            return tool_error("uri is required")
        if not content:
            return tool_error("content is required")
        mode = str(args.get("mode") or "create").strip().lower()
        if mode not in {"create", "replace", "append"}:
            return tool_error("mode must be create, replace, or append")
        payload = {
            "user_id": self._user_id,
            "user_key": self._user_key,
            "uri": uri,
            "content": content,
            "mode": mode,
            "wait": self._arg_bool(args.get("wait"), True),
        }
        return json.dumps(self._client.post("/memory-system/memories", payload), ensure_ascii=False)

    def _tool_memory_delete(self, args: Dict[str, Any]) -> str:
        uri = str(args.get("uri", "")).strip()
        if not uri:
            return tool_error("uri is required")
        recursive = self._arg_bool(args.get("recursive"), False)
        path = self._query_path(
            "/memory-system/memories",
            {
                "user_id": self._user_id,
                "user_key": self._user_key,
                "uri": uri,
                "recursive": str(recursive).lower(),
            },
        )
        return json.dumps(self._client.delete(path), ensure_ascii=False)

    def _arg_bool(self, value: Any, default: bool) -> bool:
        if isinstance(value, bool):
            return value
        if value is None:
            return default
        return _bool_value(str(value), default)


def register(ctx) -> None:
    ctx.register_memory_provider(MemorySystemMemoryProvider())
