"""End-to-end coverage for plugin registration ownership and reload cleanup."""

from __future__ import annotations

from concurrent.futures import ThreadPoolExecutor, TimeoutError
from pathlib import Path
from threading import Event
from time import monotonic, sleep
from types import MethodType

import hermes_yaml as yaml


def _write_plugin(hermes_home: Path) -> None:
    plugin_dir = hermes_home / "plugins" / "ledger_probe"
    plugin_dir.mkdir(parents=True)
    (plugin_dir / "plugin.yaml").write_text(
        yaml.safe_dump(
            {
                "name": "ledger_probe",
                "version": "0.1.0",
                "description": "ownership ledger probe",
            }
        )
    )
    (plugin_dir / "SKILL.md").write_text("# Ledger probe\n")
    (plugin_dir / "__init__.py").write_text(
        "from pathlib import Path\n"
        "\n"
        "def _hook(**kwargs):\n"
        "    return {'hook': 'ledger'}\n"
        "\n"
        "def _middleware(**kwargs):\n"
        "    return {'middleware': 'ledger'}\n"
        "\n"
        "UNLOADED = []\n"
        "\n"
        "def register(ctx):\n"
        "    ctx.register_tool(\n"
        "        name='ledger_probe_tool',\n"
        "        toolset='plugin_ledger_probe',\n"
        "        schema={'name': 'ledger_probe_tool', 'parameters': {'type': 'object', 'properties': {}}},\n"
        "        handler=lambda args, **kwargs: 'ledger',\n"
        "    )\n"
        "    ctx.register_platform(\n"
        "        name='ledger_probe_platform',\n"
        "        label='Ledger probe',\n"
        "        adapter_factory=lambda config: object(),\n"
        "        check_fn=lambda: True,\n"
        "    )\n"
        "    ctx.register_cli_command(\n"
        "        'ledger-probe-cli', 'Ledger CLI', lambda parser: None,\n"
        "        handler_fn=lambda args: None,\n"
        "    )\n"
        "    ctx.register_command(\n"
        "        'ledger-probe-command', lambda args: args,\n"
        "        description='Ledger command',\n"
        "    )\n"
        "    ctx.register_hook('pre_tool_call', _hook)\n"
        "    ctx.register_middleware('tool_request', _middleware)\n"
        "    ctx.register_auxiliary_task(\n"
        "        key='ledger_probe_task',\n"
        "        display_name='Ledger probe task',\n"
        "        description='Ledger task',\n"
        "    )\n"
        "    ctx.register_skill(\n"
        "        'ledger-probe', Path(__file__).with_name('SKILL.md'),\n"
        "        'Ledger skill',\n"
        "    )\n"
        "    ctx.register_system_prompt_section(\n"
        "        'ledger-probe-section', 'ledger probe section content',\n"
        "    )\n"
        "    ctx.register_approval_transport(\n"
        "        'ledger_probe_transport', lambda request: 'deny',\n"
        "    )\n"
        "    ctx.on_unload(lambda: UNLOADED.append('ledger_probe'))\n"
    )
    (hermes_home / "config.yaml").write_text(
        yaml.safe_dump({"plugins": {"enabled": ["ledger_probe"]}})
    )


def _write_profile_probe(hermes_home: Path, marker: str) -> None:
    plugin_dir = hermes_home / "plugins" / "profile_probe"
    plugin_dir.mkdir(parents=True)
    (plugin_dir / "plugin.yaml").write_text(
        yaml.safe_dump(
            {
                "name": "profile_probe",
                "version": "0.1.0",
                "description": f"profile probe {marker}",
            }
        )
    )
    (plugin_dir / "__init__.py").write_text(
        "def register(ctx):\n"
        "    ctx.register_tool(\n"
        "        name='shared_profile_tool',\n"
        "        toolset='profile_probe',\n"
        "        schema={'name': 'shared_profile_tool', 'parameters': {'type': 'object', 'properties': {}}},\n"
        f"        handler=lambda args, **kwargs: {marker!r},\n"
        "    )\n"
        "    ctx.register_platform(\n"
        "        name='shared_profile_platform',\n"
        f"        label={marker!r},\n"
        f"        adapter_factory=lambda config: {marker!r},\n"
        "        check_fn=lambda: True,\n"
        "    )\n"
    )
    (hermes_home / "config.yaml").write_text(
        yaml.safe_dump({"plugins": {"enabled": ["profile_probe"]}})
    )


def test_load_force_reload_and_unload_remove_every_manager_registration(
    tmp_path,
    monkeypatch,
):
    """A real temporary plugin has one live registration after each reload."""
    import hermes_cli.plugins as plugins_mod
    from gateway.platform_registry import platform_registry
    from hermes_cli.plugins import PluginManager
    from tools.registry import registry

    hermes_home = tmp_path / "hermes"
    _write_plugin(hermes_home)
    monkeypatch.setenv("HERMES_HOME", str(hermes_home))
    monkeypatch.setattr(
        plugins_mod,
        "get_bundled_plugins_dir",
        lambda: tmp_path / "empty-bundled",
    )
    monkeypatch.setattr(PluginManager, "_scan_entry_points", lambda self: [])

    manager = PluginManager()
    manager.discover_and_load()

    first_tool = registry.get_entry("ledger_probe_tool")
    first_platform = platform_registry.get("ledger_probe_platform")
    first_hook = manager._hooks["pre_tool_call"][0]
    first_middleware = manager._middleware["tool_request"][0]
    first_command = manager._plugin_commands["ledger-probe-command"]
    first_cli_command = manager._cli_commands["ledger-probe-cli"]
    first_skill = manager._plugin_skills["ledger_probe:ledger-probe"]
    module_name = manager._plugins["ledger_probe"].module.__name__

    assert first_tool is not None
    assert first_platform is not None
    assert set(registration.kind for registration in manager._ownership_ledger["ledger_probe"]) == {
        "tool",
        "platform",
        "cli_command",
        "command",
        "hook",
        "middleware",
        "auxiliary_task",
        "skill",
        "tool_override_policy",
        "system_prompt_section",
        "approval_transport",
        "on_unload",
    }
    assert "ledger-probe-section" in manager._system_prompt_sections
    assert "ledger_probe_transport" in manager._approval_transports

    manager.discover_and_load(force=True)

    second_tool = registry.get_entry("ledger_probe_tool")
    second_platform = platform_registry.get("ledger_probe_platform")
    assert second_tool is not None and second_tool is not first_tool
    assert second_platform is not None and second_platform is not first_platform
    assert second_tool.handler is not first_tool.handler
    assert first_hook not in manager._hooks["pre_tool_call"]
    assert first_middleware not in manager._middleware["tool_request"]
    assert len(manager._hooks["pre_tool_call"]) == 1
    assert len(manager._middleware["tool_request"]) == 1
    assert manager._plugin_commands["ledger-probe-command"] is not first_command
    assert manager._cli_commands["ledger-probe-cli"] is not first_cli_command
    assert manager._plugin_skills["ledger_probe:ledger-probe"] is not first_skill
    assert len(manager._aux_tasks) == 1
    assert [
        entry
        for entry in platform_registry.plugin_entries()
        if entry.name == "ledger_probe_platform"
    ] == [second_platform]

    assert manager.unload("ledger_probe") is True
    assert registry.get_entry("ledger_probe_tool") is None
    assert not platform_registry.is_registered("ledger_probe_platform")
    assert "pre_tool_call" not in manager._hooks
    assert "tool_request" not in manager._middleware
    assert "ledger-probe-command" not in manager._plugin_commands
    assert "ledger-probe-cli" not in manager._cli_commands
    assert "ledger_probe:ledger-probe" not in manager._plugin_skills
    assert "ledger-probe-section" not in manager._system_prompt_sections
    assert "ledger_probe_transport" not in manager._approval_transports
    assert manager._plugins == {} or "ledger_probe" not in manager._plugins
    reloaded_module_name = None
    import sys as _sys
    for _name, _mod in list(_sys.modules.items()):
        if getattr(_mod, "UNLOADED", None) and "ledger_probe" in _mod.UNLOADED:
            reloaded_module_name = _name
            break
    assert reloaded_module_name is not None, "on_unload callback never fired"
    assert manager._aux_tasks == {}
    assert manager._ownership_ledger == {}
    assert registry.snapshot_plugin_override_policy(
        module_name, scope=manager.scope_key
    ) is None
    assert registry.plugin_scope_for_module(module_name) == manager.scope_key


def test_reverse_unload_restores_an_overridden_platform_registration():
    """Reverse teardown reveals an older entry before removing it."""
    from gateway.platform_registry import platform_registry
    from hermes_cli.plugins import PluginContext, PluginManager, PluginManifest

    name = "ledger_override_platform"
    scope = platform_registry.current_scope_key()
    previous = platform_registry.snapshot_registration(name, scope=scope)
    manager_a = PluginManager(scope_key=scope)
    manager_b = PluginManager(scope_key=scope)
    context_a = PluginContext(
        PluginManifest(name="ledger_owner_a", key="ledger_owner_a"), manager_a
    )
    context_b = PluginContext(
        PluginManifest(name="ledger_owner_b", key="ledger_owner_b"), manager_b
    )

    try:
        handle_a = context_a.register_platform(
            name=name,
            label="Ledger A",
            adapter_factory=lambda config: "a",
            check_fn=lambda: True,
        )
        entry_a = platform_registry.get(name)
        handle_b = context_b.register_platform(
            name=name,
            label="Ledger B",
            adapter_factory=lambda config: "b",
            check_fn=lambda: True,
        )
        entry_b = platform_registry.get(name)

        assert handle_a is not None and handle_b is not None
        assert entry_a is not None and entry_b is not None
        assert entry_a is not entry_b

        handle_b.dispose()
        assert platform_registry.get(name) is entry_a
        handle_a.dispose()
        assert platform_registry.snapshot_registration(name, scope=scope) == previous
    finally:
        # The test uses a deliberately unique name, but restore any state that
        # a surrounding test may have installed under it.
        current = platform_registry.snapshot_registration(name, scope=scope)
        platform_registry.restore_registration(
            name, current, previous, scope=scope
        )


def test_targeted_unload_does_not_resurrect_an_older_tool_override():
    """The tool overlay follows the same arbitrary-order ownership contract."""
    from hermes_cli.plugins import PluginContext, PluginManager, PluginManifest
    from tools.registry import registry

    name = "ledger_out_of_order_tool"
    scope = registry.current_scope_key()
    previous = registry.snapshot_registration(name, scope=scope)
    manager_a = PluginManager(scope_key=scope)
    manager_b = PluginManager(scope_key=scope)
    context_a = PluginContext(PluginManifest(name="tool_a", key="tool_a"), manager_a)
    context_b = PluginContext(PluginManifest(name="tool_b", key="tool_b"), manager_b)

    def register(context, marker):
        return context.register_tool(
            name=name,
            toolset="ledger_test",
            schema={
                "name": name,
                "parameters": {"type": "object", "properties": {}},
            },
            handler=lambda args, **kwargs: marker,
        )

    try:
        assert register(context_a, "a") is not None
        old_entry = registry.get_entry(name, scope=scope)
        assert register(context_b, "b") is not None
        new_entry = registry.get_entry(name, scope=scope)

        assert old_entry is not None and new_entry is not old_entry
        manager_a.unload("tool_a")
        assert registry.get_entry(name, scope=scope) is new_entry
        assert name not in manager_a._plugin_tool_names
        manager_b.unload("tool_b")
        assert registry.get_entry(name, scope=scope) is not old_entry
        assert registry.snapshot_registration(name, scope=scope) is previous
    finally:
        current = registry.snapshot_registration(name, scope=scope)
        if current is not None:
            registry.restore_registration(name, current, previous, scope=scope)


def test_rejected_tool_registration_does_not_claim_global_fallback():
    """Effective fallback identity cannot masquerade as a successful write."""
    from hermes_cli.plugins import PluginContext, PluginManager, PluginManifest
    from tools.registry import registry

    name = "ledger_rejected_tool"
    previous = registry.snapshot_registration(name)

    def shared_handler(args, **kwargs):
        return "base"

    registry.register(
        name=name,
        toolset="ledger_base",
        schema={"name": name, "parameters": {"type": "object", "properties": {}}},
        handler=shared_handler,
    )
    base_entry = registry.snapshot_registration(name)
    manager = PluginManager()
    context = PluginContext(
        PluginManifest(name="rejected_owner", key="rejected_owner"), manager
    )
    try:
        handle = context.register_tool(
            name=name,
            toolset="different_toolset",
            schema={
                "name": name,
                "parameters": {"type": "object", "properties": {}},
            },
            handler=shared_handler,
        )
        assert handle is None
        assert name not in manager._plugin_tool_names
        assert manager._ownership_ledger == {}
        assert registry.snapshot_registration(name) is base_entry
    finally:
        if base_entry is not None:
            registry.restore_registration(name, base_entry, previous)


def test_plugin_context_cannot_shadow_same_toolset_global_with_core_callable():
    """Explicit context scope cannot launder an imported/core handler."""
    from hermes_cli.plugins import PluginContext, PluginManager, PluginManifest
    from tools.registry import registry

    name = "ledger_same_toolset_global"
    previous = registry.snapshot_registration(name)
    registry.register(
        name=name,
        toolset="ledger_same_toolset",
        schema={"name": name, "parameters": {"type": "object", "properties": {}}},
        handler=lambda args, **kwargs: "base",
    )
    base_entry = registry.snapshot_registration(name)
    manager = PluginManager()
    context = PluginContext(
        PluginManifest(name="core_callable", key="core_callable"), manager
    )
    try:
        assert context.register_tool(
            name=name,
            toolset="ledger_same_toolset",
            schema={
                "name": name,
                "parameters": {"type": "object", "properties": {}},
            },
            handler=str,
        ) is None
        assert registry.get_entry(name, scope=manager.scope_key) is base_entry
        assert registry.snapshot_registration(name, scope=manager.scope_key) is None
    finally:
        if base_entry is not None:
            registry.restore_registration(name, base_entry, previous)


def test_rejected_tool_registration_does_not_claim_local_predecessor():
    """A same-handler rejection cannot manufacture a replacement lease."""
    from hermes_cli.plugins import PluginContext, PluginManager, PluginManifest
    from tools.registry import registry

    name = "ledger_rejected_local_tool"
    scope = registry.current_scope_key()
    previous = registry.snapshot_registration(name, scope=scope)

    def shared_handler(args, **kwargs):
        return "shared"

    manager_a = PluginManager(scope_key=scope)
    manager_b = PluginManager(scope_key=scope)
    context_a = PluginContext(PluginManifest(name="local_owner", key="local_owner"), manager_a)
    context_b = PluginContext(PluginManifest(name="false_owner", key="false_owner"), manager_b)
    try:
        assert context_a.register_tool(
            name=name,
            toolset="owner_toolset",
            schema={"name": name, "parameters": {"type": "object", "properties": {}}},
            handler=shared_handler,
        ) is not None
        owner_entry = registry.snapshot_registration(name, scope=scope)
        assert context_b.register_tool(
            name=name,
            toolset="different_toolset",
            schema={"name": name, "parameters": {"type": "object", "properties": {}}},
            handler=shared_handler,
        ) is None
        assert manager_b._ownership_ledger == {}
        manager_a.unload("local_owner")
        assert registry.snapshot_registration(name, scope=scope) is previous
        assert owner_entry is not None
    finally:
        current = registry.snapshot_registration(name, scope=scope)
        if current is not None:
            registry.restore_registration(name, current, previous, scope=scope)


def test_scoped_plugin_cannot_deregister_a_process_global_tool():
    """Profile-local plugin cleanup must never mutate the shared base layer."""
    from unittest.mock import patch

    import pytest

    from tools.registry import ToolRegistry

    registry = ToolRegistry()
    name = "ledger_global_base"
    registry.register(
        name=name,
        toolset="ledger_base",
        schema={"name": name, "parameters": {"type": "object", "properties": {}}},
        handler=lambda args, **kwargs: "base",
    )
    module_name = "hermes_plugins.scoped_cleanup"
    registry.register_plugin_override_policy(
        module_name, True, scope="/profiles/isolated"
    )

    with patch.object(ToolRegistry, "_caller_module", return_value=module_name):
        with pytest.raises(PermissionError, match="process-global"):
            registry.deregister(name)

    assert registry.snapshot_registration(name) is not None


def test_shared_entrypoint_module_uses_the_active_profile_scope(tmp_path):
    """One pip module can serve A and B without becoming process-global."""
    import pytest

    from hermes_constants import reset_hermes_home_override, set_hermes_home_override
    from tools.registry import ToolRegistry

    registry = ToolRegistry()
    module_name = "third_party.shared_hermes_plugin"
    home_a = str((tmp_path / "entrypoint-a").resolve())
    home_b = str((tmp_path / "entrypoint-b").resolve())
    home_c = str((tmp_path / "entrypoint-c").resolve())
    policy_a = registry.register_plugin_override_policy(
        module_name, False, scope=home_a
    )
    policy_b = registry.register_plugin_override_policy(
        module_name, False, scope=home_b
    )
    handler = eval("lambda args, **kwargs: 'shared'", {"__name__": module_name})

    def register_in(home, name):
        token = set_hermes_home_override(home)
        try:
            registry.register(
                name=name,
                toolset="entrypoint_shared",
                schema={
                    "name": name,
                    "parameters": {"type": "object", "properties": {}},
                },
                handler=handler,
            )
        finally:
            reset_hermes_home_override(token)

    register_in(home_a, "shared_entrypoint_a")
    register_in(home_b, "shared_entrypoint_b")
    assert registry.snapshot_registration("shared_entrypoint_a", scope=home_a) is not None
    assert registry.snapshot_registration("shared_entrypoint_a", scope=home_b) is None
    assert registry.snapshot_registration("shared_entrypoint_b", scope=home_b) is not None
    assert registry.snapshot_registration("shared_entrypoint_b") is None

    from unittest.mock import patch

    token = set_hermes_home_override(home_a)
    try:
        with patch.object(ToolRegistry, "_caller_module", return_value=module_name):
            registry.deregister("shared_entrypoint_a")
    finally:
        reset_hermes_home_override(token)
    assert registry.snapshot_registration("shared_entrypoint_a", scope=home_a) is None
    assert registry.snapshot_registration("shared_entrypoint_b", scope=home_b) is not None

    # Policy unload revokes authorization but durable scope attribution keeps
    # a stale delayed callback out of the process-global registry.
    registry.restore_plugin_override_policy(
        module_name, policy_a, None, scope=home_a
    )
    registry.restore_plugin_override_policy(
        module_name, policy_b, None, scope=home_b
    )
    register_in(home_a, "shared_entrypoint_stale")
    assert registry.snapshot_registration("shared_entrypoint_stale", scope=home_a)
    assert registry.snapshot_registration("shared_entrypoint_stale") is None
    with pytest.raises(PermissionError, match="multiple profiles"):
        register_in(home_c, "shared_entrypoint_ambiguous")
    assert registry.snapshot_registration("shared_entrypoint_ambiguous") is None


def test_decorated_plugin_callable_keeps_its_defining_module_scope(tmp_path):
    """functools.wraps must not replace the plugin wrapper's provenance."""
    import functools

    from tools.registry import ToolRegistry

    registry = ToolRegistry()
    module_name = "third_party.decorated_plugin"
    scope = str((tmp_path / "decorated-profile").resolve())
    registry.register_plugin_override_policy(module_name, False, scope=scope)
    namespace = {"__name__": module_name, "functools": functools}
    exec(
        "@functools.wraps(str)\n"
        "def handler(args, **kwargs):\n"
        "    return 'decorated'\n",
        namespace,
    )
    handler = namespace["handler"]
    registry.register(
        name="decorated_plugin_tool",
        toolset="decorated_plugin",
        schema={
            "name": "decorated_plugin_tool",
            "parameters": {"type": "object", "properties": {}},
        },
        handler=handler,
    )

    assert registry.snapshot_registration(
        "decorated_plugin_tool", scope=scope
    ) is not None
    assert registry.snapshot_registration("decorated_plugin_tool") is None


def test_entrypoint_policy_uses_the_most_specific_module_prefix(tmp_path):
    from hermes_constants import reset_hermes_home_override, set_hermes_home_override
    from tools.registry import ToolRegistry

    registry = ToolRegistry()
    broad_scope = str((tmp_path / "broad").resolve())
    narrow_scope = str((tmp_path / "narrow").resolve())
    registry.register_plugin_override_policy("vendor", True, scope=broad_scope)
    registry.register_plugin_override_policy(
        "vendor.plugin", False, scope=narrow_scope
    )
    handler = eval(
        "lambda args, **kwargs: 'narrow'",
        {"__name__": "vendor.plugin.handlers"},
    )
    token = set_hermes_home_override(narrow_scope)
    try:
        registry.register(
            name="specific_entrypoint_tool",
            toolset="specific_entrypoint",
            schema={
                "name": "specific_entrypoint_tool",
                "parameters": {"type": "object", "properties": {}},
            },
            handler=handler,
        )
    finally:
        reset_hermes_home_override(token)

    assert registry.snapshot_registration(
        "specific_entrypoint_tool", scope=narrow_scope
    ) is not None
    assert registry.snapshot_registration(
        "specific_entrypoint_tool", scope=broad_scope
    ) is None


def test_targeted_unload_does_not_resurrect_an_older_override():
    """Removing A under B tombstones A so B cannot restore it later."""
    from gateway.platform_registry import platform_registry
    from hermes_cli.plugins import PluginContext, PluginManager, PluginManifest

    name = "ledger_out_of_order_platform"
    scope = platform_registry.current_scope_key()
    previous = platform_registry.snapshot_registration(name, scope=scope)
    manager_a = PluginManager(scope_key=scope)
    manager_b = PluginManager(scope_key=scope)
    context_a = PluginContext(
        PluginManifest(name="ledger_old", key="ledger_old"), manager_a
    )
    context_b = PluginContext(
        PluginManifest(name="ledger_new", key="ledger_new"), manager_b
    )

    try:
        context_a.register_platform(
            name=name,
            label="old",
            adapter_factory=lambda config: "old",
            check_fn=lambda: True,
        )
        old_entry = platform_registry.get(name)
        context_b.register_platform(
            name=name,
            label="new",
            adapter_factory=lambda config: "new",
            check_fn=lambda: True,
        )
        new_entry = platform_registry.get(name)

        assert old_entry is not None and new_entry is not None
        assert manager_a.unload("ledger_old") is True
        assert platform_registry.get(name) is new_entry
        assert name not in manager_a._plugin_platform_names
        assert manager_b.unload("ledger_new") is True
        assert platform_registry.get(name) is not old_entry
        assert platform_registry.snapshot_registration(name, scope=scope) == previous
    finally:
        current = platform_registry.snapshot_registration(name, scope=scope)
        platform_registry.restore_registration(
            name, current, previous, scope=scope
        )


def test_manager_local_override_does_not_resurrect_after_targeted_unload():
    from hermes_cli.plugins import PluginContext, PluginManager, PluginManifest

    manager = PluginManager()
    context_a = PluginContext(PluginManifest(name="local_a", key="local_a"), manager)
    context_b = PluginContext(PluginManifest(name="local_b", key="local_b"), manager)

    context_a.register_cli_command("shared-local", "A", lambda parser: None)
    context_b.register_cli_command("shared-local", "B", lambda parser: None)

    assert manager.unload("local_a") is True
    assert manager._cli_commands["shared-local"]["plugin"] == "local_b"
    assert manager.unload("local_b") is True
    assert "shared-local" not in manager._cli_commands


def test_provider_overlay_switches_profiles_and_reveals_fresh_global_fallback(
    tmp_path,
    monkeypatch,
):
    """Provider consumers see A→B→A, and unload never pins a stale base."""
    from agent.image_gen_provider import ImageGenProvider
    import agent.image_gen_registry as image_registry
    from hermes_constants import reset_hermes_home_override, set_hermes_home_override
    from hermes_cli.plugins import PluginContext, PluginManager, PluginManifest

    class Provider(ImageGenProvider):
        def __init__(self, marker):
            self.marker = marker

        @property
        def name(self):
            return "ledger_profile_provider"

        def generate(self, prompt, aspect_ratio="landscape", **kwargs):
            return {"marker": self.marker}

    name = "ledger_profile_provider"
    baseline = image_registry.snapshot_registration(name)
    global_a = Provider("global-a")
    global_b = Provider("global-b")
    provider_a = Provider("profile-a")
    provider_b = Provider("profile-b")
    home_a = str((tmp_path / "provider-a").resolve())
    home_b = str((tmp_path / "provider-b").resolve())
    manager_a = PluginManager(scope_key=home_a)
    manager_b = PluginManager(scope_key=home_b)
    context_a = PluginContext(PluginManifest(name="provider_a", key="provider_a"), manager_a)
    context_b = PluginContext(PluginManifest(name="provider_b", key="provider_b"), manager_b)
    monkeypatch.setattr(
        "hermes_cli.config.load_config_readonly",
        lambda: {"image_gen": {"provider": name}},
    )

    def active_for(home):
        token = set_hermes_home_override(home)
        try:
            return image_registry.get_active_provider()
        finally:
            reset_hermes_home_override(token)

    image_registry.register_provider(global_a)
    try:
        assert context_a.register_image_gen_provider(provider_a) is not None
        assert context_b.register_image_gen_provider(provider_b) is not None
        assert active_for(home_a) is provider_a
        assert active_for(home_b) is provider_b
        assert active_for(home_a) is provider_a

        assert manager_a.unload("provider_a") is True
        assert active_for(home_a) is global_a
        image_registry.register_provider(global_b)
        assert active_for(home_a) is global_b
        assert active_for(home_b) is provider_b

        assert manager_b.unload("provider_b") is True
        assert active_for(home_b) is global_b
    finally:
        manager_a.unload("provider_a")
        manager_b.unload("provider_b")
        current = image_registry.snapshot_registration(name)
        if current is not None:
            image_registry.restore_registration(name, current, baseline)


def test_reused_provider_singleton_keeps_registration_generations_distinct():
    """Reusing one object after unload must not revive its retired generation."""
    from agent.image_gen_provider import ImageGenProvider
    from agent.image_gen_registry import (
        get_provider,
        restore_registration,
        snapshot_registration,
    )
    from hermes_cli.plugins import PluginContext, PluginManager, PluginManifest

    class ProbeProvider(ImageGenProvider):
        def __init__(self, marker):
            self.marker = marker

        @property
        def name(self):
            return "ledger_generation_provider"

        def generate(self, prompt, aspect_ratio="landscape", **kwargs):
            return {"marker": self.marker}

    managers = [PluginManager() for _ in range(4)]
    scope = managers[0].scope_key
    baseline = get_provider("ledger_generation_provider", scope=scope)
    baseline_local = snapshot_registration("ledger_generation_provider", scope=scope)
    provider_a = ProbeProvider("a")
    provider_b = ProbeProvider("b")
    provider_c = ProbeProvider("c")
    contexts = [
        PluginContext(
            PluginManifest(name=f"provider_{index}", key=f"provider_{index}"),
            manager,
        )
        for index, manager in enumerate(managers)
    ]

    try:
        contexts[0].register_image_gen_provider(provider_a)
        contexts[1].register_image_gen_provider(provider_b)
        managers[0].unload("provider_0")
        assert get_provider(provider_a.name, scope=scope) is provider_b

        # A fresh ownership generation deliberately reuses the same singleton.
        contexts[2].register_image_gen_provider(provider_a)
        contexts[3].register_image_gen_provider(provider_c)
        managers[3].unload("provider_3")
        assert get_provider(provider_a.name, scope=scope) is provider_a
        managers[2].unload("provider_2")
        assert get_provider(provider_a.name, scope=scope) is provider_b
        managers[1].unload("provider_1")
        assert get_provider(provider_a.name, scope=scope) is baseline
    finally:
        current = snapshot_registration("ledger_generation_provider", scope=scope)
        if current is not baseline_local and current is not None:
            restore_registration(
                "ledger_generation_provider", current, baseline_local, scope=scope
            )


def test_same_provider_singleton_can_have_two_live_owners():
    """Retiring an older identical lease must not remove the newer owner."""
    from agent.image_gen_provider import ImageGenProvider
    from agent.image_gen_registry import (
        get_provider,
        restore_registration,
        snapshot_registration,
    )
    from hermes_cli.plugins import PluginContext, PluginManager, PluginManifest

    class SharedProvider(ImageGenProvider):
        @property
        def name(self):
            return "ledger_shared_singleton"

        def generate(self, prompt, aspect_ratio="landscape", **kwargs):
            return {"prompt": prompt}

    provider = SharedProvider()
    manager_a = PluginManager()
    manager_b = PluginManager()
    scope = manager_a.scope_key
    baseline = get_provider("ledger_shared_singleton", scope=scope)
    baseline_local = snapshot_registration("ledger_shared_singleton", scope=scope)
    context_a = PluginContext(PluginManifest(name="shared_a", key="shared_a"), manager_a)
    context_b = PluginContext(PluginManifest(name="shared_b", key="shared_b"), manager_b)

    try:
        assert context_a.register_image_gen_provider(provider) is not None
        assert context_b.register_image_gen_provider(provider) is not None
        manager_a.unload("shared_a")
        assert get_provider(provider.name, scope=scope) is provider
        manager_b.unload("shared_b")
        assert get_provider(provider.name, scope=scope) is baseline
    finally:
        current = snapshot_registration(provider.name, scope=scope)
        if current is not baseline_local and current is not None:
            restore_registration(
                provider.name, current, baseline_local, scope=scope
            )


def test_provider_cleanup_uses_the_captured_normalized_name():
    """Whitespace and later name mutation cannot orphan a provider entry."""
    from agent.image_gen_provider import ImageGenProvider
    from agent.image_gen_registry import (
        get_provider,
        restore_registration,
        snapshot_registration,
    )
    from hermes_cli.plugins import PluginContext, PluginManager, PluginManifest

    class MutableProvider(ImageGenProvider):
        def __init__(self):
            self._name = "  ledger_mutable_provider  "

        @property
        def name(self):
            return self._name

        def generate(self, prompt, aspect_ratio="landscape", **kwargs):
            return {"prompt": prompt}

    key = "ledger_mutable_provider"
    provider = MutableProvider()
    manager = PluginManager()
    scope = manager.scope_key
    baseline = get_provider(key, scope=scope)
    baseline_local = snapshot_registration(key, scope=scope)
    context = PluginContext(
        PluginManifest(name="mutable_provider", key="mutable_provider"), manager
    )
    try:
        assert context.register_image_gen_provider(provider) is not None
        assert get_provider(key, scope=scope) is provider
        provider._name = "renamed_after_registration"
        manager.unload("mutable_provider")
        assert get_provider(key, scope=scope) is baseline
    finally:
        current = snapshot_registration(key, scope=scope)
        if current is not baseline_local and current is not None:
            restore_registration(key, current, baseline_local, scope=scope)


def test_registration_transaction_excludes_concurrent_disposal(monkeypatch):
    """A lease cannot be retired between another generation's write/acquire."""
    from agent.image_gen_provider import ImageGenProvider
    import agent.image_gen_registry as image_registry
    from hermes_cli.plugins import PluginContext, PluginManager, PluginManifest

    class Provider(ImageGenProvider):
        def __init__(self, marker):
            self.marker = marker

        @property
        def name(self):
            return "ledger_transaction_provider"

        def generate(self, prompt, aspect_ratio="landscape", **kwargs):
            return {"marker": self.marker}

    provider_a = Provider("a")
    provider_b = Provider("b")
    manager_a = PluginManager()
    manager_b = PluginManager()
    scope = manager_a.scope_key
    baseline = image_registry.get_provider("ledger_transaction_provider", scope=scope)
    baseline_local = image_registry.snapshot_registration(
        "ledger_transaction_provider", scope=scope
    )
    context_a = PluginContext(PluginManifest(name="tx_a", key="tx_a"), manager_a)
    context_b = PluginContext(PluginManifest(name="tx_b", key="tx_b"), manager_b)
    context_a.register_image_gen_provider(provider_a)

    original_register = image_registry.register_provider
    wrote = Event()
    release = Event()

    def paused_register(provider, *, scope=None):
        original_register(provider, scope=scope)
        if provider is provider_b:
            wrote.set()
            assert release.wait(timeout=2)

    monkeypatch.setattr(image_registry, "register_provider", paused_register)
    try:
        with ThreadPoolExecutor(max_workers=2) as pool:
            registration = pool.submit(context_b.register_image_gen_provider, provider_b)
            assert wrote.wait(timeout=1)
            disposal = pool.submit(manager_a.unload, "tx_a")
            try:
                disposal.result(timeout=0.05)
            except TimeoutError:
                pass
            else:
                raise AssertionError("dispose interleaved with registration transaction")
            release.set()
            assert registration.result(timeout=1) is not None
            assert disposal.result(timeout=1) is True

        assert image_registry.get_provider(provider_a.name, scope=scope) is provider_b
        manager_b.unload("tx_b")
        assert image_registry.get_provider(provider_a.name, scope=scope) is baseline
    finally:
        release.set()
        current = image_registry.snapshot_registration(
            "ledger_transaction_provider", scope=scope
        )
        if current is not baseline_local and current is not None:
            image_registry.restore_registration(
                "ledger_transaction_provider",
                current,
                baseline_local,
                scope=scope,
            )


def test_deferred_platform_resolution_is_atomic_across_threads():
    """Concurrent first lookups both observe the materialized adapter."""
    from gateway.platform_registry import PlatformEntry, PlatformRegistry

    registry = PlatformRegistry()
    entry = PlatformEntry(
        name="threaded_platform",
        label="Threaded",
        adapter_factory=lambda config: object(),
        check_fn=lambda: True,
        source="plugin",
    )
    loads = []
    started = Event()
    release = Event()

    def load():
        loads.append(1)
        started.set()
        assert release.wait(timeout=2)
        registry.register(entry)

    registry.register_deferred("threaded_platform", load)
    with ThreadPoolExecutor(max_workers=3) as pool:
        first = pool.submit(registry.get, "threaded_platform")
        assert started.wait(timeout=1)
        assert registry.is_registered("threaded_platform")
        second = pool.submit(registry.get, "threaded_platform")
        try:
            second.result(timeout=0.05)
        except TimeoutError:
            pass
        else:
            raise AssertionError("concurrent lookup did not wait for materialization")
        enumeration = pool.submit(registry.all_entries)
        try:
            enumeration.result(timeout=0.05)
        except TimeoutError:
            pass
        else:
            raise AssertionError("enumeration omitted an in-flight platform")
        release.set()
        results = [first.result(timeout=1), second.result(timeout=1)]
        assert enumeration.result(timeout=1) == [entry]

    assert results == [entry, entry]
    assert loads == [1]


def test_deferred_platform_recursive_lookup_does_not_deadlock():
    """A loader that asks for its own entry fails fast until registration."""
    from gateway.platform_registry import PlatformEntry, PlatformRegistry

    registry = PlatformRegistry()
    nested_results = []
    entry = PlatformEntry(
        name="recursive_platform",
        label="Recursive",
        adapter_factory=lambda config: object(),
        check_fn=lambda: True,
        source="plugin",
    )

    def load():
        nested_results.append(registry.get("recursive_platform"))
        registry.register(entry)

    registry.register_deferred("recursive_platform", load)
    assert registry.get("recursive_platform") is entry
    assert nested_results == [None]


def test_resolved_deferred_platform_restores_its_displaced_loader():
    """Deferred-to-concrete loading remains one replacement chain."""
    from gateway.platform_registry import platform_registry
    from hermes_cli.plugins import PluginContext, PluginManager, PluginManifest

    name = "ledger_transfer"
    scope = platform_registry.current_scope_key()
    previous = platform_registry.snapshot_registration(name, scope=scope)
    old_loader = lambda: None
    platform_registry.register_deferred(name, old_loader, scope=scope)
    displaced = platform_registry.snapshot_registration(name, scope=scope)
    manager = PluginManager(scope_key=scope)
    manifest = PluginManifest(
        name=f"{name}-platform",
        key=f"{name}-platform",
        source="bundled",
        path="unused",
    )

    def load_scoped(self, loaded_manifest):
        PluginContext(loaded_manifest, self).register_platform(
            name=name,
            label="Transferred",
            adapter_factory=lambda config: object(),
            check_fn=lambda: True,
        )

    manager._load_plugin_scoped = MethodType(load_scoped, manager)
    try:
        manager._register_deferred_platform(manifest)
        entry = platform_registry.get(name)
        assert entry is not None and entry.label == "Transferred"
        manager.unload(manifest)
        assert platform_registry.snapshot_registration(name, scope=scope) == displaced
    finally:
        current = platform_registry.snapshot_registration(name, scope=scope)
        platform_registry.restore_registration(
            name, current, previous, scope=scope
        )


def test_unload_cancels_a_deferred_platform_before_module_load():
    """Losing the in-flight race cannot publish registrations after unload."""
    from gateway.platform_registry import platform_registry
    from hermes_cli.plugins import PluginContext, PluginManager, PluginManifest

    name = "ledger_cancel"
    scope = platform_registry.current_scope_key()
    previous = platform_registry.snapshot_registration(name, scope=scope)
    def old_loader():
        from gateway.platform_registry import PlatformEntry

        platform_registry.register(
            PlatformEntry(
                name=name,
                label="Restored predecessor",
                adapter_factory=lambda config: object(),
                check_fn=lambda: True,
                source="plugin",
            ),
            scope=scope,
        )
    platform_registry.register_deferred(name, old_loader, scope=scope)
    displaced = platform_registry.snapshot_registration(name, scope=scope)
    manager = PluginManager(scope_key=scope)
    manifest = PluginManifest(
        name=f"{name}-platform",
        key=f"{name}-platform",
        source="bundled",
        path="unused",
    )

    def load_scoped(self, loaded_manifest):
        PluginContext(loaded_manifest, self).register_platform(
            name=name,
            label="Should not publish",
            adapter_factory=lambda config: object(),
            check_fn=lambda: True,
        )

    manager._load_plugin_scoped = MethodType(load_scoped, manager)
    manager._register_deferred_platform(manifest)
    try:
        with ThreadPoolExecutor(max_workers=1) as pool:
            with manager._discovery_lock:
                lookup = pool.submit(platform_registry.get, name)
                deadline = monotonic() + 1
                while (scope, name) not in platform_registry._inflight:
                    if monotonic() >= deadline:
                        raise AssertionError("deferred loader never became in-flight")
                    sleep(0.001)
                assert manager.unload(manifest) is True
            restored_entry = lookup.result(timeout=1)
        assert restored_entry is not None
        assert restored_entry.label == "Restored predecessor"
        assert platform_registry.snapshot_registration(name, scope=scope)[0] is restored_entry
        assert name not in manager._plugin_platform_names
    finally:
        current = platform_registry.snapshot_registration(name, scope=scope)
        platform_registry.restore_registration(
            name, current, previous, scope=scope
        )


def test_direct_plugin_platform_registration_infers_immutable_scope(tmp_path):
    """The documented direct registry API cannot leak into another profile."""
    from gateway.platform_registry import PlatformEntry, platform_registry
    from hermes_constants import reset_hermes_home_override, set_hermes_home_override
    from tools.registry import registry as tool_registry

    home_a = str((tmp_path / "direct-a").resolve())
    home_b = tmp_path / "direct-b"
    module_name = "company.hermes.direct_platform_probe"
    policy = tool_registry.register_plugin_override_policy(
        module_name, False, scope=home_a
    )
    factory = eval(
        "lambda config: 'direct-a'",
        {"__name__": f"{module_name}.handlers"},
    )
    name = "ledger_direct_platform"
    previous_a = platform_registry.snapshot_registration(name, scope=home_a)
    previous_global = platform_registry.snapshot_registration(name)
    token = set_hermes_home_override(home_b)
    try:
        platform_registry.register(
            PlatformEntry(
                name=name,
                label="Direct A",
                adapter_factory=factory,
                check_fn=lambda: True,
                source="plugin",
            )
        )
        assert platform_registry.get(name) is None
    finally:
        reset_hermes_home_override(token)

    token = set_hermes_home_override(home_a)
    try:
        assert platform_registry.get(name).label == "Direct A"
    finally:
        reset_hermes_home_override(token)
        current_a = platform_registry.snapshot_registration(name, scope=home_a)
        platform_registry.restore_registration(
            name, current_a, previous_a, scope=home_a
        )
        current_global = platform_registry.snapshot_registration(name)
        if current_global != previous_global:
            platform_registry.restore_registration(
                name, current_global, previous_global
            )
        tool_registry.restore_plugin_override_policy(
            module_name, policy, None, scope=home_a
        )


def test_same_name_tool_and_platform_are_isolated_by_hermes_home(
    tmp_path,
    monkeypatch,
):
    """Real A→B→A profile switching keeps dispatch and adapters isolated."""
    import hermes_cli.plugins as plugins_mod
    from gateway.platform_registry import platform_registry
    from hermes_constants import reset_hermes_home_override, set_hermes_home_override
    from hermes_cli.plugins import PluginManager
    from tools.registry import registry

    home_a = tmp_path / "profile-a"
    home_b = tmp_path / "profile-b"
    _write_profile_probe(home_a, "profile-a")
    _write_profile_probe(home_b, "profile-b")
    monkeypatch.setattr(
        plugins_mod,
        "get_bundled_plugins_dir",
        lambda: tmp_path / "empty-bundled",
    )
    monkeypatch.setattr(PluginManager, "_scan_entry_points", lambda self: [])

    def load_profile(home: Path):
        token = set_hermes_home_override(home)
        try:
            manager = plugins_mod.get_plugin_manager()
            manager.discover_and_load()
            tool_entry = registry.get_entry("shared_profile_tool")
            platform_entry = platform_registry.get("shared_profile_platform")
            return manager, tool_entry, platform_entry
        finally:
            reset_hermes_home_override(token)

    manager_a, tool_a, platform_a = load_profile(home_a)
    manager_b, tool_b, platform_b = load_profile(home_b)

    assert manager_a is not manager_b
    assert tool_a is not None and tool_b is not None and tool_a is not tool_b
    assert platform_a is not None and platform_b is not None and platform_a is not platform_b

    token_a = set_hermes_home_override(home_a)
    try:
        assert registry.dispatch("shared_profile_tool", {}) == "profile-a"
        assert platform_registry.get("shared_profile_platform") is platform_a
    finally:
        reset_hermes_home_override(token_a)

    token_b = set_hermes_home_override(home_b)
    try:
        assert registry.dispatch("shared_profile_tool", {}) == "profile-b"
        assert platform_registry.get("shared_profile_platform") is platform_b
    finally:
        reset_hermes_home_override(token_b)

    token_a = set_hermes_home_override(home_a)
    try:
        assert registry.dispatch("shared_profile_tool", {}) == "profile-a"
        assert platform_registry.get("shared_profile_platform") is platform_a
    finally:
        reset_hermes_home_override(token_a)


def test_manager_discovery_uses_its_home_not_the_ambient_profile(
    tmp_path,
    monkeypatch,
):
    """A retained manager cannot scan another concurrently active profile."""
    import hermes_cli.plugins as plugins_mod
    from gateway.platform_registry import platform_registry
    from hermes_constants import reset_hermes_home_override, set_hermes_home_override
    from hermes_cli.plugins import PluginManager
    from tools.registry import registry

    home_a = tmp_path / "retained-a"
    home_b = tmp_path / "ambient-b"
    _write_profile_probe(home_a, "retained-a")
    _write_profile_probe(home_b, "ambient-b")
    monkeypatch.setattr(
        plugins_mod,
        "get_bundled_plugins_dir",
        lambda: tmp_path / "empty-bundled",
    )
    monkeypatch.setattr(PluginManager, "_scan_entry_points", lambda self: [])

    manager_a = PluginManager(scope_key=str(home_a.resolve()))
    ambient = set_hermes_home_override(home_b)
    try:
        manager_a.discover_and_load()
    finally:
        reset_hermes_home_override(ambient)

    tool_a = registry.get_entry("shared_profile_tool", scope=manager_a.scope_key)
    platform_a = platform_registry.snapshot_registration(
        "shared_profile_platform", scope=manager_a.scope_key
    )[0]
    assert tool_a is not None and tool_a.handler({}) == "retained-a"
    assert platform_a is not None and platform_a.label == "retained-a"
    assert registry.snapshot_registration(
        "shared_profile_tool", scope=str(home_b.resolve())
    ) is None
    assert platform_registry.snapshot_registration(
        "shared_profile_platform", scope=str(home_b.resolve())
    ) == (None, None)


def test_same_slug_profiles_allocate_distinct_modules_concurrently(
    tmp_path,
    monkeypatch,
):
    """Policy binding and import use one atomic profile-specific namespace."""
    import hermes_cli.plugins as plugins_mod
    from hermes_cli.plugins import PluginManager

    home_a = tmp_path / "concurrent-a"
    home_b = tmp_path / "concurrent-b"
    _write_profile_probe(home_a, "concurrent-a")
    _write_profile_probe(home_b, "concurrent-b")
    monkeypatch.setattr(
        plugins_mod,
        "get_bundled_plugins_dir",
        lambda: tmp_path / "empty-bundled",
    )
    monkeypatch.setattr(PluginManager, "_scan_entry_points", lambda self: [])
    managers = [
        PluginManager(scope_key=str(home_a.resolve())),
        PluginManager(scope_key=str(home_b.resolve())),
    ]

    with ThreadPoolExecutor(max_workers=2) as pool:
        list(pool.map(lambda manager: manager.discover_and_load(), managers))

    modules = [manager._plugins["profile_probe"].module.__name__ for manager in managers]
    assert modules[0] != modules[1]


def test_spawned_supervised_task_is_cancelled_on_unload():
    """A plugin-spawned background task is tracked and cancelled on unload."""
    import asyncio

    from hermes_cli.plugins import PluginContext, PluginManager, PluginManifest

    manager = PluginManager()
    manifest = PluginManifest(
        name="task_probe", version="0.1", description="", source="user",
    )
    ctx = PluginContext(manifest, manager)
    cancelled = []

    async def scenario():
        async def forever():
            try:
                await asyncio.sleep(3600)
            except asyncio.CancelledError:
                cancelled.append(True)
                raise

        task = ctx.spawn_task(forever(), name="probe-task")
        await asyncio.sleep(0)
        kinds = {
            registration.kind
            for registration in manager._ownership_ledger["task_probe"]
        }
        assert "background_task" in kinds
        assert manager.unload("task_probe") is True
        with __import__("pytest").raises(asyncio.CancelledError):
            await task
        # Done-callback disposal removes the handle from the ledger.
        await asyncio.sleep(0)
        assert "task_probe" not in manager._ownership_ledger

    asyncio.run(scenario())
    assert cancelled == [True]


def test_on_unload_exception_does_not_block_other_teardown():
    """A raising on_unload callback is isolated; later cleanup still runs."""
    from hermes_cli.plugins import PluginContext, PluginManager, PluginManifest

    manager = PluginManager()
    manifest = PluginManifest(
        name="boom_probe", version="0.1", description="", source="user",
    )
    ctx = PluginContext(manifest, manager)
    order = []

    ctx.on_unload(lambda: order.append("first"))

    def _boom():
        order.append("boom")
        raise RuntimeError("cleanup failed")

    ctx.on_unload(_boom)
    ctx.on_unload(lambda: order.append("last"))

    assert manager.unload("boom_probe") is True
    # Reverse acquisition order, exception isolated.
    assert order == ["last", "boom", "first"]
    assert "boom_probe" not in manager._ownership_ledger
