# Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
# Licensed under AGPL-3.0-or-later - see LICENSE file
# Part of SuperLocalMemory V3 | https://qualixar.com | https://varunpratap.com

"""HTTP proxy that lets MCP processes use the daemon as their worker.

Without this, every MCP process (one per IDE) would spawn its own
``recall_worker`` subprocess through ``WorkerPool.shared()`` and load
the ONNX embedder into that subprocess. With N IDEs open the total
RSS was approximately N x 1.6 GB — the exact failure Path B was built
to avoid.

With this proxy, the MCP process opens an HTTP connection to the
single long-lived daemon (already running for dashboard / mesh /
health) and forwards ``recall`` and ``store`` calls there. Heavy
engine state exists in exactly one process: the daemon.
"""
from __future__ import annotations

import json
import logging
import urllib.parse
import urllib.request
from typing import Any

logger = logging.getLogger(__name__)


class DaemonPoolProxy:
    """:class:`WorkerPool`-shaped facade that talks to the daemon over HTTP.

    The shape matches ``WorkerPool.recall`` / ``WorkerPool.store`` so that
    the existing pool adapter in ``mcp/_pool_adapter.py`` can swap between
    a local subprocess pool and the daemon proxy without any adapter
    change. Errors are returned as ``{"ok": False, "error": "..."}``
    envelopes — the adapter is responsible for surfacing those.
    """

    def __init__(self, port: int, *, timeout_s: float = 30.0) -> None:
        self._port = port
        self._timeout = timeout_s

    def _url(self, path: str) -> str:
        return f"http://127.0.0.1:{self._port}{path}"

    def recall(
        self, query: str, limit: int = 10, session_id: str = "",
        fast: bool = False,
    ) -> dict[str, Any]:
        params = urllib.parse.urlencode({
            "q": query,
            "limit": limit,
            "session_id": session_id or "",
            "fast": "true" if fast else "false",
        })
        try:
            with urllib.request.urlopen(
                self._url(f"/recall?{params}"), timeout=self._timeout,
            ) as resp:
                data = json.loads(resp.read().decode() or "{}")
        except Exception as exc:
            logger.warning("daemon /recall failed: %s", exc)
            return {"ok": False, "error": str(exc)}
        if not isinstance(data, dict):
            return {"ok": False, "error": "non-dict response"}
        data.setdefault("ok", True)
        return data

    def store(
        self, content: str, metadata: dict | None = None,
    ) -> dict[str, Any]:
        body = json.dumps({
            "content": content,
            "tags": (metadata or {}).get("tags", ""),
            "metadata": metadata or {},
        }).encode()
        req = urllib.request.Request(
            self._url("/remember"),
            data=body,
            headers={"Content-Type": "application/json"},
            method="POST",
        )
        try:
            with urllib.request.urlopen(req, timeout=self._timeout) as resp:
                data = json.loads(resp.read().decode() or "{}")
        except Exception as exc:
            logger.warning("daemon /remember failed: %s", exc)
            return {"ok": False, "error": str(exc)}
        if not isinstance(data, dict):
            return {"ok": False, "error": "non-dict response"}
        data.setdefault("ok", True)
        return data


def choose_pool() -> Any:
    """Return the best available pool for this MCP process.

    Preference order:
      1. Running daemon — use HTTP proxy (keeps ONNX in ONE process)
      2. No daemon — fall back to ``WorkerPool.shared()`` (spawns a
         local subprocess with a FULL engine). This keeps single-user
         / first-launch scenarios working.
    """
    try:
        from superlocalmemory.cli.daemon import _get_port, is_daemon_running
        if is_daemon_running():
            return DaemonPoolProxy(port=_get_port())
    except Exception as exc:
        logger.warning("daemon probe failed — falling back to subprocess pool: %s", exc)
    from superlocalmemory.core.worker_pool import WorkerPool
    return WorkerPool.shared()
