"""插件能力投影的隔离契约测试。"""

from types import SimpleNamespace

from app.runtime.extensions.plugin.projection import PluginProjection


class _Plugin(SimpleNamespace):
    """提供可配置插件 hook 的最小运行态插件替身。"""

    def __init__(self, enabled=True, **hooks):
        """保存启用状态、插件名称和 hook 实现。"""
        super().__init__(plugin_name=hooks.pop("plugin_name", "测试插件"), **hooks)
        self._enabled = enabled

    def get_state(self):
        """返回预设启用状态。"""
        return self._enabled

    def get_name(self):
        """返回插件展示名称。"""
        return self.plugin_name


def test_projection_preserves_commands_and_api_adaptation():
    """命令补 pid，API 补宿主路径与默认认证方式。"""
    command = {"cmd": "/demo"}
    api = {"path": "/items", "endpoint": object()}
    plugin = _Plugin(
        get_command=lambda: [command],
        get_api=lambda: [api],
    )
    projection = PluginProjection({"Demo": plugin})

    assert projection.commands() == [{"cmd": "/demo", "pid": "Demo"}]
    assert projection.apis() == [{
        "path": "/Demo/items",
        "endpoint": api["endpoint"],
        "auth": "apikey",
    }]
    assert api == {"path": "/items", "endpoint": api["endpoint"]}


def test_projection_filters_disabled_stateful_hooks_but_keeps_api_contract():
    """禁用插件不暴露命令/服务/模块/动作，API 保持历史上的独立注册语义。"""
    plugin = _Plugin(
        enabled=False,
        get_command=lambda: [{"cmd": "/demo"}],
        get_api=lambda: [{"path": "/items"}],
        get_service=lambda: [{"id": "job"}],
        get_module=lambda: {"recognize": object()},
        get_actions=lambda: [{"id": "action"}],
    )
    projection = PluginProjection({"Demo": plugin})

    assert projection.commands() == []
    assert projection.services() == []
    assert projection.modules() == {}
    assert projection.actions() == []
    assert projection.apis() == [{"path": "/Demo/items", "auth": "apikey"}]


def test_projection_preserves_services_modules_actions_and_pid_filter():
    """指定 pid 时只投影目标插件，并保持各 hook 的原始结构。"""
    demo = _Plugin(
        get_service=lambda: [{"id": "job"}],
        get_module=lambda: {"recognize": "handler"},
        get_actions=lambda: [{"id": "action"}],
    )
    other = _Plugin(get_service=lambda: [{"id": "other"}])
    projection = PluginProjection({"Demo": demo, "Other": other})

    assert projection.services("Demo") == [{"id": "job"}]
    assert projection.modules("Demo") == {
        ("Demo", "测试插件"): {"recognize": "handler"}
    }
    assert projection.actions("Demo") == [{
        "plugin_id": "Demo",
        "plugin_name": "测试插件",
        "actions": [{"id": "action"}],
    }]


def test_projection_modules_skips_none_and_non_mapping_declarations():
    """get_module 未返回映射的插件被跳过并记日志，不污染整体模块投影。"""
    errors = []
    log = SimpleNamespace(error=lambda message: errors.append(message))
    projection = PluginProjection(
        {
            "NoDecl": _Plugin(get_module=lambda: None),
            "ListDecl": _Plugin(get_module=lambda: ["not-a-mapping"]),
            "Healthy": _Plugin(get_module=lambda: {"recognize": "handler"}),
        },
        log=log,
    )

    assert projection.modules() == {("Healthy", "测试插件"): {"recognize": "handler"}}
    assert any("ListDecl" in message for message in errors)


def test_projection_collects_enabled_media_source_declarations():
    """只投影启用插件的媒体来源声明，并附带插件 ID 便于诊断。"""
    demo = _Plugin(
        get_media_source=lambda: [{
            "name": "Acme Video",
            "media_source": "acme.video",
            "media_types": ["电影", "电视剧"],
        }],
    )
    disabled = _Plugin(
        enabled=False,
        get_media_source=lambda: [{"name": "Disabled", "media_source": "disabled"}],
    )
    projection = PluginProjection({"Demo": demo, "Disabled": disabled})

    assert projection.media_sources() == [{
        "name": "Acme Video",
        "media_source": "acme.video",
        "media_types": ["电影", "电视剧"],
        "plugin_id": "Demo",
    }]


def test_projection_isolates_one_plugin_hook_failure():
    """单个插件 hook 失败只记日志，不阻断其他插件投影。"""
    errors = []
    log = SimpleNamespace(error=lambda message: errors.append(message))

    def fail():
        """模拟插件 hook 抛出异常。"""
        raise RuntimeError("broken")

    projection = PluginProjection(
        {
            "Broken": _Plugin(get_service=fail),
            "Healthy": _Plugin(get_service=lambda: [{"id": "healthy"}]),
        },
        log=log,
    )

    assert projection.services() == [{"id": "healthy"}]
    assert errors and "Broken" in errors[0]


def test_projection_builds_federation_and_auth_provider_entries():
    """联邦远程入口和插件认证入口保持既有字段与默认值。"""
    plugin = _Plugin(
        get_render_mode=lambda: ("vue", "dist/assets"),
        get_auth_providers=lambda: [{"id": "demo-login"}],
    )
    projection = PluginProjection(
        {"Demo": plugin},
        remote_entry_factory=lambda plugin_id, path: f"/{plugin_id}/{path}",
    )

    assert projection.remotes() == [{
        "id": "Demo",
        "url": "/Demo/dist/assets",
        "name": "测试插件",
    }]
    assert projection.auth_providers() == [{
        "id": "demo-login",
        "type": "plugin",
        "plugin_id": "Demo",
        "name": "测试插件",
        "enabled": True,
        "component": "AuthPage",
        "remote": {
            "id": "Demo",
            "url": "/Demo/dist/assets",
            "name": "测试插件",
        },
    }]


def test_projection_exposes_source_identity_for_virtual_frontend_instance():
    """虚拟实例的联邦入口保留实例 URL，并补充共享源码身份。"""
    plugin = _Plugin(
        plugin_source_id="Demo",
        get_render_mode=lambda: ("vue", "dist/assets"),
        get_auth_providers=lambda: [{"id": "demo-login"}],
    )
    projection = PluginProjection(
        {"DemoWork": plugin},
        remote_entry_factory=lambda plugin_id, path: f"/{plugin_id}/{path}",
    )

    assert projection.remotes()[0] == {
        "id": "DemoWork",
        "url": "/DemoWork/dist/assets",
        "name": "测试插件",
        "source_plugin_id": "Demo",
    }
    assert projection.auth_providers()[0]["remote"]["source_plugin_id"] == "Demo"


def test_projection_normalizes_sidebar_and_dashboard_metadata():
    """侧栏和仪表板元数据在投影层完成校验、排序与兼容默认值。"""
    plugin = _Plugin(
        get_render_mode=lambda: ("vue", "dist"),
        get_sidebar_nav=lambda: [{
            "key": "settings",
            "section": "invalid",
            "permission": "invalid",
            "order": "3",
        }],
        get_dashboard=lambda: ({}, {}, []),
        get_dashboard_meta=lambda: [{"name": "状态", "key": "status"}],
    )
    projection = PluginProjection({"Demo": plugin})

    assert projection.sidebar() == [{
        "plugin_id": "Demo",
        "nav_key": "settings",
        "title": "测试插件",
        "icon": "mdi-puzzle",
        "section": "system",
        "permission": None,
        "order": 3,
    }]
    assert projection.dashboard_metadata() == [{
        "id": "Demo",
        "name": "状态",
        "key": "status",
    }]
