"""通用系统设置入口的插件 mutation 准入测试。"""

import asyncio
import json
from types import SimpleNamespace
from unittest.mock import AsyncMock, MagicMock

import pytest

from app.agent.tools.impl.update_system_settings import UpdateSystemSettingsTool
from app.api.endpoints import system as system_endpoint
from app.application.plugin import runtime as plugin_runtime
from app.runtime.extensions.plugin.admission import PluginMutationAdmission
from app.schemas.types import SystemConfigKey


PLUGIN_RUNTIME_KEYS = (
    SystemConfigKey.UserInstalledPlugins,
    SystemConfigKey.PluginInstances,
    SystemConfigKey.PluginFolders,
)


@pytest.mark.asyncio
@pytest.mark.parametrize("config_key", PLUGIN_RUNTIME_KEYS)
async def test_system_http_rejects_plugin_keys_after_admission_seal(
    config_key: SystemConfigKey,
    monkeypatch,
) -> None:
    """HTTP 通用设置入口在封口后不得写入插件运行态配置。"""
    admission = PluginMutationAdmission()
    admission.seal()
    config = MagicMock()
    config.async_set = AsyncMock()
    monkeypatch.setattr(
        plugin_runtime,
        "get_plugin_manager",
        lambda: SimpleNamespace(mutation=admission.hold),
    )
    monkeypatch.setattr(
        system_endpoint,
        "get_runtime_settings",
        lambda: SimpleNamespace(contains=lambda _key: False),
    )
    monkeypatch.setattr(
        system_endpoint,
        "get_configured_system_config",
        lambda: config,
    )

    response = await system_endpoint.set_setting(config_key.value, {}, None)

    assert response.success is False
    assert "停机阶段" in response.message
    config.async_set.assert_not_awaited()


@pytest.mark.asyncio
@pytest.mark.parametrize("config_key", PLUGIN_RUNTIME_KEYS)
async def test_agent_rejects_plugin_keys_after_admission_seal(
    config_key: SystemConfigKey,
    monkeypatch,
) -> None:
    """Agent 通用设置入口在封口后不得读取或写入插件运行态配置。"""
    admission = PluginMutationAdmission()
    admission.seal()
    config = MagicMock()
    config.async_set = AsyncMock()
    monkeypatch.setattr(
        plugin_runtime,
        "get_plugin_manager",
        lambda: SimpleNamespace(mutation=admission.hold),
    )
    tool = UpdateSystemSettingsTool(
        session_id="session-1",
        user_id="10001",
        system_config=config,
    )

    payload = json.loads(await tool.run(setting_key=config_key.value, value={}))

    assert payload["success"] is False
    assert "停机阶段" in payload["message"]
    config.get.assert_not_called()
    config.async_set.assert_not_awaited()


@pytest.mark.asyncio
async def test_system_http_plugin_write_remains_owned_until_settled(
    monkeypatch,
) -> None:
    """HTTP 在途插件配置写入完成前，封口等待不得误判为空闲。"""
    admission = PluginMutationAdmission()
    write_started = asyncio.Event()
    write_release = asyncio.Event()

    async def async_set(_key: str, _value: object) -> bool:
        """阻塞配置写入，暴露 quiesce 等待窗口。"""
        write_started.set()
        await write_release.wait()
        return True

    config = MagicMock()
    config.async_set = AsyncMock(side_effect=async_set)
    monkeypatch.setattr(
        plugin_runtime,
        "get_plugin_manager",
        lambda: SimpleNamespace(mutation=admission.hold),
    )
    monkeypatch.setattr(
        system_endpoint,
        "get_runtime_settings",
        lambda: SimpleNamespace(contains=lambda _key: False),
    )
    monkeypatch.setattr(
        system_endpoint,
        "get_configured_system_config",
        lambda: config,
    )
    monkeypatch.setattr(system_endpoint.eventmanager, "async_send_event", AsyncMock())

    task = asyncio.create_task(
        system_endpoint.set_setting(
            SystemConfigKey.UserInstalledPlugins.value,
            ["DemoPlugin"],
            None,
        )
    )
    await write_started.wait()
    assert admission.seal() == 1
    idle_waiter = asyncio.create_task(asyncio.to_thread(admission.wait_until_idle))
    await asyncio.sleep(0.02)
    assert idle_waiter.done() is False

    write_release.set()
    response = await task
    await idle_waiter
    assert response.success is True
    assert admission.active_count == 0


@pytest.mark.asyncio
async def test_agent_plugin_write_remains_owned_until_saved_value_read(
    monkeypatch,
) -> None:
    """Agent 插件配置事务在写入和结果读取完成前持续持有 lease。"""
    admission = PluginMutationAdmission()
    write_started = asyncio.Event()
    write_release = asyncio.Event()

    async def async_set(_key: SystemConfigKey, _value: object) -> bool:
        """阻塞 Agent 配置写入，暴露 quiesce 等待窗口。"""
        write_started.set()
        await write_release.wait()
        return True

    config = MagicMock()
    config.get.side_effect = [[], ["DemoPlugin"]]
    config.async_set = AsyncMock(side_effect=async_set)
    monkeypatch.setattr(
        plugin_runtime,
        "get_plugin_manager",
        lambda: SimpleNamespace(mutation=admission.hold),
    )
    monkeypatch.setattr(
        "app.agent.tools.impl.update_system_settings.eventmanager.async_send_event",
        AsyncMock(),
    )
    tool = UpdateSystemSettingsTool(
        session_id="session-1",
        user_id="10001",
        system_config=config,
    )

    task = asyncio.create_task(
        tool.run(
            setting_key=SystemConfigKey.UserInstalledPlugins.value,
            value=["DemoPlugin"],
        )
    )
    await write_started.wait()
    assert admission.seal() == 1
    idle_waiter = asyncio.create_task(asyncio.to_thread(admission.wait_until_idle))
    await asyncio.sleep(0.02)
    assert idle_waiter.done() is False

    write_release.set()
    payload = json.loads(await task)
    await idle_waiter
    assert payload["success"] is True
    assert payload["saved_value"] == ["DemoPlugin"]
    assert admission.active_count == 0


@pytest.mark.asyncio
async def test_non_plugin_system_config_does_not_resolve_plugin_runtime(
    monkeypatch,
) -> None:
    """非插件 SystemConfig 写入保持原路径且不依赖插件组合根。"""
    config = MagicMock()
    config.async_set = AsyncMock(return_value=True)

    def fail_runtime_resolution():
        """若非插件配置误取插件运行时则立即暴露回归。"""
        raise AssertionError("非插件配置不应解析插件运行时")

    monkeypatch.setattr(plugin_runtime, "get_plugin_manager", fail_runtime_resolution)
    monkeypatch.setattr(
        system_endpoint,
        "get_runtime_settings",
        lambda: SimpleNamespace(contains=lambda _key: False),
    )
    monkeypatch.setattr(
        system_endpoint,
        "get_configured_system_config",
        lambda: config,
    )
    monkeypatch.setattr(system_endpoint.eventmanager, "async_send_event", AsyncMock())

    response = await system_endpoint.set_setting(
        SystemConfigKey.Directories.value,
        [],
        None,
    )

    assert response.success is True
    config.async_set.assert_awaited_once_with(SystemConfigKey.Directories.value, None)


@pytest.mark.asyncio
async def test_agent_non_plugin_config_does_not_resolve_plugin_runtime(
    monkeypatch,
) -> None:
    """Agent 非插件配置写入保持既有读写和事件语义。"""
    config = MagicMock()
    config.get.side_effect = [{}, {"chatgpt": {"enabled": True}}]
    config.async_set = AsyncMock(return_value=True)

    def fail_runtime_resolution():
        """若 Agent 非插件配置误取插件运行时则立即暴露回归。"""
        raise AssertionError("非插件配置不应解析插件运行时")

    monkeypatch.setattr(plugin_runtime, "get_plugin_manager", fail_runtime_resolution)
    monkeypatch.setattr(
        "app.agent.tools.impl.update_system_settings.eventmanager.async_send_event",
        AsyncMock(),
    )
    tool = UpdateSystemSettingsTool(
        session_id="session-1",
        user_id="10001",
        system_config=config,
    )

    payload = json.loads(
        await tool.run(
            setting_key=SystemConfigKey.AIAgentConfig.value,
            value={"chatgpt": {"enabled": True}},
        )
    )

    assert payload["success"] is True
    assert payload["changed"] is True
    config.async_set.assert_awaited_once()
