"""Move a user from a memory provider that left core onto its catalog plugin.

A bundled ``plugins/memory/<name>`` that becomes a standalone catalog plugin keeps the same provider
name, config section (``memory.<name>``), data directory and tool names, so the migration is only
"the code now lives under ``HERMES_HOME/plugins/<name>``". Two hooks call :func:`migrate_home`:

* ``hermes update`` — for every profile home that shares the venv (primary; runs where the venv was
  just rebuilt anyway).
* agent init — when the configured provider cannot be found at all, once per process (Desktop
  users update through the app and never run ``hermes update`` by hand).

Both install the catalog entry at its reviewed pin through the normal plugin install path (kill
list, dependency constraints, enable), never a custom source. Offline or absent from the catalog:
the user gets the exact one-liner instead of silently running without memory.
"""

from __future__ import annotations

import logging
from pathlib import Path
from typing import Callable, Optional

logger = logging.getLogger(__name__)

_attempted: set[tuple[str, str]] = set()


def configured_provider(home: Path) -> str:
    """``memory.provider`` of *home*'s effective config, or ``""``."""
    from pm.plugins_state import read_home_selection
    memory = (read_home_selection(home) or {}).get("memory") or {}
    return str(memory.get("provider") or "").strip()


def provider_present(name: str, home: Path) -> bool:
    """True when the provider resolves anywhere Hermes looks for *home* (bundled, that home's user
    plugins, entry point). The lookup reads the active home, so it is bound explicitly: the update
    hook walks several profile homes from one process."""
    from plugins.memory import find_provider_dir
    from hermes_constants import reset_hermes_home_override, set_hermes_home_override
    token = set_hermes_home_override(home)
    try:
        return find_provider_dir(name) is not None
    finally:
        reset_hermes_home_override(token)


def catalog_source(name: str) -> Optional[str]:
    """The catalog entry that ships provider *name*, or None when the catalog has no such plugin."""
    from hermes_cli.plugin_catalog import get_live_catalog_entry
    entry = get_live_catalog_entry(name)
    return entry.name if entry is not None else None


def migrate_home(home: Path, *, install: Callable[[str], dict], say: Callable[[str], None] = print) -> Optional[str]:
    """Install the configured provider's catalog plugin into *home* when the provider is gone.

    Returns the installed plugin name, or None when nothing needed doing or the install could not
    happen (already reported through *say*). Never raises: memory being down must not take the
    update or the agent down with it.
    """
    name = configured_provider(home)
    from agent.memory_provider import is_core_memory_provider
    if is_core_memory_provider(name) or provider_present(name, home):
        return None
    if catalog_source(name) is None:
        say(f"  ⚠ Memory provider '{name}' is configured but not installed and not in the plugin catalog. "
            f"Install it with `hermes plugins install <source>` or change memory.provider.")
        return None
    try:
        result = install(name)
    except Exception as exc:  # network, uv, kill list — report, do not raise
        result = {"ok": False, "error": str(exc)}
    if result.get("ok"):
        say(f"  ✓ Memory provider '{name}' moved out of core — installed its plugin from the catalog "
            f"(your memory.{name} settings and data are unchanged).")
        return name
    say(f"  ⚠ Memory provider '{name}' moved out of core and could not be installed automatically: "
        f"{result.get('error') or 'unknown error'}. Run `hermes plugins install {name}`.")
    return None


def _install_into(home: Path) -> Callable[[str], dict]:
    def _install(name: str) -> dict:
        from hermes_cli.plugins_cmd import dashboard_install_plugin
        from hermes_constants import reset_hermes_home_override, set_hermes_home_override
        token = set_hermes_home_override(home)
        try:
            return dashboard_install_plugin("", force=False, enable=True, catalog_name=name)
        finally:
            reset_hermes_home_override(token)
    return _install


def migrate_all_homes(*, say: Callable[[str], None] = print) -> list[str]:
    """``hermes update`` hook: every profile home sharing this venv. Returns installed plugin names."""
    from pm.plugins_state import dependency_homes
    installed: list[str] = []
    for home in dependency_homes():
        try:
            name = migrate_home(home, install=_install_into(home), say=say)
        except Exception as exc:
            logger.debug("memory provider migration skipped for %s: %s", home, exc)
            continue
        if name:
            installed.append(name)
    return installed


def recover_at_startup(name: str) -> bool:
    """Agent-init hook for a configured provider that resolved nowhere. One attempt per process per
    home and name; honours ``security.allow_lazy_installs`` because it installs code. True when installed."""
    from hermes_constants import get_hermes_home, hermes_home_key

    home = get_hermes_home()
    key = (hermes_home_key(home), name)
    if key in _attempted:
        return False
    _attempted.add(key)
    from pm.install import lazy_installs_allowed
    if not lazy_installs_allowed():
        logger.warning("Memory provider '%s' is not installed; security.allow_lazy_installs is off — "
                       "run `hermes plugins install %s`.", name, name)
        return False
    return migrate_home(home, install=_install_into(home), say=logger.warning) == name
