from types import SimpleNamespace
from typing import Any
import importlib.util
import sys

import pytest

from src.core.tooling import ToolAvailabilityContext, ToolRegistry
from src.maisaka.tool_provider import MaisakaBuiltinToolProvider
from src.plugin_runtime.component_query import ComponentQueryService
from src.plugin_runtime.host.component_registry import ComponentRegistry


@pytest.mark.asyncio
async def test_builtin_at_tool_is_not_exposed() -> None:
    registry = ToolRegistry()
    registry.register_provider(MaisakaBuiltinToolProvider())

    group_specs = await registry.list_tools(ToolAvailabilityContext(session_id="group-1", is_group_chat=True))
    private_specs = await registry.list_tools(ToolAvailabilityContext(session_id="private-1", is_group_chat=False))
    default_specs = await registry.list_tools()

    assert "at" not in {tool_spec.name for tool_spec in group_specs}
    assert "at" not in {tool_spec.name for tool_spec in private_specs}
    assert "at" not in {tool_spec.name for tool_spec in default_specs}


def test_plugin_tool_chat_scope_uses_component_field(monkeypatch: pytest.MonkeyPatch) -> None:
    service = ComponentQueryService()
    registry = ComponentRegistry()
    supervisor = SimpleNamespace(component_registry=registry)
    monkeypatch.setattr(service, "_iter_supervisors", lambda: [supervisor])

    registry.register_plugin_components(
        "scope_plugin",
        [
            {
                "name": "group_tool",
                "component_type": "TOOL",
                "chat_scope": "group",
                "metadata": {"description": "group only"},
            },
            {
                "name": "private_tool",
                "component_type": "TOOL",
                "chat_scope": "private",
                "metadata": {"description": "private only"},
            },
            {
                "name": "all_tool",
                "component_type": "TOOL",
                "metadata": {"description": "all chats"},
            },
        ],
    )

    group_specs = service.get_llm_available_tool_specs(
        context=ToolAvailabilityContext(session_id="group-1", is_group_chat=True)
    )
    private_specs = service.get_llm_available_tool_specs(
        context=ToolAvailabilityContext(session_id="private-1", is_group_chat=False)
    )

    group_entry = registry.get_component("scope_plugin.group_tool")
    assert group_entry is not None
    assert group_entry.chat_scope == "group"
    assert "chat_scope" not in group_entry.metadata
    assert set(group_specs) == {"group_tool", "all_tool"}
    assert set(private_specs) == {"private_tool", "all_tool"}


def test_plugin_tool_session_disable_still_filters_specific_chat(monkeypatch: pytest.MonkeyPatch) -> None:
    service = ComponentQueryService()
    registry = ComponentRegistry()
    supervisor = SimpleNamespace(component_registry=registry)
    monkeypatch.setattr(service, "_iter_supervisors", lambda: [supervisor])

    registry.register_plugin_components(
        "mute_plugin",
        [
            {
                "name": "mute",
                "component_type": "TOOL",
                "chat_scope": "group",
                "metadata": {"description": "mute group member"},
            }
        ],
    )
    registry.set_component_enabled("mute_plugin.mute", False, session_id="group-disabled")

    disabled_specs = service.get_llm_available_tool_specs(
        context=ToolAvailabilityContext(session_id="group-disabled", is_group_chat=True)
    )
    enabled_specs = service.get_llm_available_tool_specs(
        context=ToolAvailabilityContext(session_id="group-enabled", is_group_chat=True)
    )

    assert "mute" not in disabled_specs
    assert "mute" in enabled_specs


def test_plugin_tool_allowed_session_filters_tool_exposure(monkeypatch: pytest.MonkeyPatch) -> None:
    service = ComponentQueryService()
    registry = ComponentRegistry()
    supervisor = SimpleNamespace(component_registry=registry)
    monkeypatch.setattr(service, "_iter_supervisors", lambda: [supervisor])

    registry.register_plugin_components(
        "mute_plugin",
        [
            {
                "name": "mute",
                "component_type": "TOOL",
                "chat_scope": "group",
                "allowed_session": ["qq:10001", "raw-group-id", "exact-session-id"],
                "metadata": {"description": "mute group member"},
            }
        ],
    )

    platform_group_specs = service.get_llm_available_tool_specs(
        context=ToolAvailabilityContext(
            session_id="hashed-session-1",
            is_group_chat=True,
            group_id="10001",
            platform="qq",
        )
    )
    raw_group_specs = service.get_llm_available_tool_specs(
        context=ToolAvailabilityContext(
            session_id="hashed-session-2",
            is_group_chat=True,
            group_id="raw-group-id",
            platform="qq",
        )
    )
    exact_session_specs = service.get_llm_available_tool_specs(
        context=ToolAvailabilityContext(session_id="exact-session-id", is_group_chat=True)
    )
    blocked_specs = service.get_llm_available_tool_specs(
        context=ToolAvailabilityContext(
            session_id="blocked-session",
            is_group_chat=True,
            group_id="20002",
            platform="qq",
        )
    )

    entry = registry.get_component("mute_plugin.mute")
    assert entry is not None
    assert entry.allowed_session == {"qq:10001", "raw-group-id", "exact-session-id"}
    assert "allowed_session" not in entry.metadata
    assert "mute" in platform_group_specs
    assert "mute" in raw_group_specs
    assert "mute" in exact_session_specs
    assert "mute" not in blocked_specs


def test_plugin_tool_disabled_session_take_precedence_over_allowed_session(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    service = ComponentQueryService()
    registry = ComponentRegistry()
    supervisor = SimpleNamespace(component_registry=registry)
    monkeypatch.setattr(service, "_iter_supervisors", lambda: [supervisor])

    registry.register_plugin_components(
        "mute_plugin",
        [
            {
                "name": "mute",
                "component_type": "TOOL",
                "chat_scope": "group",
                "allowed_session": ["qq:10001"],
                "metadata": {"description": "mute group member"},
            }
        ],
    )
    registry.set_component_enabled("mute_plugin.mute", False, session_id="allowed-session")

    visible_specs = service.get_llm_available_tool_specs(
        context=ToolAvailabilityContext(
            session_id="visible-session",
            is_group_chat=True,
            group_id="10001",
            platform="qq",
        )
    )
    disabled_specs = service.get_llm_available_tool_specs(
        context=ToolAvailabilityContext(
            session_id="allowed-session",
            is_group_chat=True,
            group_id="10001",
            platform="qq",
        )
    )

    entry = registry.get_component("mute_plugin.mute")
    assert entry is not None
    assert entry.disabled_session == {"allowed-session"}
    assert "mute" in visible_specs
    assert "mute" not in disabled_specs


def test_mute_plugin_exports_allowed_groups_as_component_allowed_session() -> None:
    module_path = "plugins/MutePlugin/plugin.py"
    spec = importlib.util.spec_from_file_location("mute_plugin_under_test", module_path)
    assert spec is not None
    assert spec.loader is not None
    module = importlib.util.module_from_spec(spec)
    sys.modules[spec.name] = module
    spec.loader.exec_module(module)
    module.MutePluginConfig.model_rebuild()

    plugin = module.MutePlugin()
    plugin.set_plugin_config({"permissions": {"allowed_groups": ["qq:10001", "raw-group-id"]}})

    mute_components = [component for component in plugin.get_components() if component.get("name") == "mute"]

    assert len(mute_components) == 1
    assert mute_components[0]["chat_scope"] == "group"
    assert mute_components[0]["allowed_session"] == ["qq:10001", "raw-group-id"]
    assert "allowed_session" not in mute_components[0]["metadata"]


@pytest.mark.asyncio
async def test_mute_tool_queries_target_message_with_current_chat_id() -> None:
    module_path = "plugins/MutePlugin/plugin.py"
    spec = importlib.util.spec_from_file_location("mute_plugin_under_test_msg_id", module_path)
    assert spec is not None
    assert spec.loader is not None
    module = importlib.util.module_from_spec(spec)
    sys.modules[spec.name] = module
    spec.loader.exec_module(module)
    module.MutePluginConfig.model_rebuild()

    capability_calls: list[dict[str, Any]] = []
    api_calls: list[dict[str, Any]] = []

    async def fake_call_capability(name: str, **kwargs: Any) -> dict[str, Any]:
        capability_calls.append({"name": name, **kwargs})
        return {
            "success": True,
            "result": {
                "success": True,
                "message": {
                    "message_info": {
                        "user_info": {
                            "user_id": "35529667",
                            "user_cardname": "目标用户",
                            "user_nickname": "目标昵称",
                        }
                    }
                },
            },
        }

    async def fake_api_call(api_name: str, **kwargs: Any) -> dict[str, Any]:
        api_calls.append({"name": api_name, **kwargs})
        if api_name == "adapter.napcat.group.get_group_member_info":
            return {"success": True, "result": {"data": {"role": "member"}}}
        return {"status": "ok", "retcode": 0}

    plugin = module.MutePlugin()
    plugin.set_plugin_config({"components": {"enable_smart_mute": True}})
    plugin._set_context(
        SimpleNamespace(
            call_capability=fake_call_capability,
            api=SimpleNamespace(call=fake_api_call),
            logger=SimpleNamespace(info=lambda *args, **kwargs: None, warning=lambda *args, **kwargs: None),
        )
    )

    success, message = await plugin.handle_mute_tool(
        stream_id="current-session-id",
        group_id="766798517",
        msg_id="2046083292",
        duration=3600,
        reason="测试",
    )

    assert success is True
    assert message == "成功禁言 目标用户"
    assert capability_calls == [
        {
            "name": "message.get_by_id",
            "message_id": "2046083292",
            "chat_id": "current-session-id",
        }
    ]
    assert api_calls[-1] == {
        "name": "adapter.napcat.group.set_group_ban",
        "version": "1",
        "group_id": "766798517",
        "user_id": "35529667",
        "duration": 3600,
    }
