from contextlib import nullcontext

from app.application.plugin.config import PluginConfigCommand
from app.runtime.extensions.plugin.admission import PluginMutationAdmission


def _command(
    calls: list[tuple],
    *,
    save_result: bool = True,
    mutation=None,
) -> PluginConfigCommand:
    """构造记录端口调用顺序的插件配置用例。"""
    return PluginConfigCommand(
        save_config=lambda plugin_id, config, force: (
            calls.append(("save", plugin_id, config, force)) or save_result
        ),
        initialize=lambda plugin_id, config: calls.append(
            ("initialize", plugin_id, config)
        ),
        stop=lambda plugin_id: calls.append(("stop", plugin_id)),
        delete_config=lambda plugin_id, force: (
            calls.append(("delete_config", plugin_id, force)) or True
        ),
        delete_data=lambda plugin_id, force: (
            calls.append(("delete_data", plugin_id, force)) or True
        ),
        reload_runtime=lambda plugin_id: calls.append(("reload", plugin_id)),
        publish_reset=lambda plugin_id: calls.append(("publish", plugin_id)),
        refresh_registrations=lambda plugin_id: calls.append(
            ("registrations", plugin_id)
        ),
        mutation=mutation or (lambda _operation: nullcontext()),
    )


def test_update_stops_before_runtime_side_effects_when_save_fails() -> None:
    """配置持久化失败时不得初始化插件或刷新宿主注册。"""
    calls: list[tuple] = []

    result = _command(calls, save_result=False).update("DemoPlugin", {"enabled": True})

    assert result.success is False
    assert result.message == "插件配置保存失败"
    assert calls == [("save", "DemoPlugin", {"enabled": True}, False)]


def test_update_refreshes_runtime_only_after_config_is_saved() -> None:
    """配置保存成功后按初始化、注册刷新顺序生效。"""
    calls: list[tuple] = []

    result = _command(calls).update("DemoPlugin", {"enabled": True})

    assert result.success is True
    assert calls == [
        ("save", "DemoPlugin", {"enabled": True}, False),
        ("initialize", "DemoPlugin", {"enabled": True}),
        ("registrations", "DemoPlugin"),
    ]


def test_reset_preserves_compensation_cleanup_and_reload_order() -> None:
    """重置必须先让插件补偿，再停止、清理并重建运行态和注册。"""
    calls: list[tuple] = []

    result = _command(calls).reset("DemoPlugin")

    assert result.success is True
    assert calls == [
        ("publish", "DemoPlugin"),
        ("stop", "DemoPlugin"),
        ("delete_config", "DemoPlugin", True),
        ("delete_data", "DemoPlugin", True),
        ("reload", "DemoPlugin"),
        ("registrations", "DemoPlugin"),
    ]


def test_sealed_config_command_rejects_before_first_side_effect() -> None:
    """配置事务在 admission 封口后返回失败，且不得先发布 reset 事件。"""
    calls: list[tuple] = []
    admission = PluginMutationAdmission()
    admission.seal()

    update_result = _command(calls, mutation=admission.hold).update(
        "DemoPlugin",
        {"enabled": True},
    )
    reset_result = _command(calls, mutation=admission.hold).reset("DemoPlugin")

    assert update_result.success is False
    assert reset_result.success is False
    assert "停机阶段" in update_result.message
    assert "停机阶段" in reset_result.message
    assert calls == []
