import asyncio
from types import SimpleNamespace
from unittest.mock import AsyncMock, Mock, patch

from app.modules.discord import DiscordModule
from app.modules.discord.discord import Discord
from app.schemas import CommandRegisterEventData


def test_discord_module_register_commands_filters_event_subset():
    """Discord 模块注册命令时应复用渠道级 CommandRegister 事件过滤结果。"""
    module = DiscordModule()
    client = SimpleNamespace(register_commands=Mock(), delete_commands=Mock())
    original_commands = {
        "/sites": {"description": "管理站点"},
        "/version": {"description": "当前版本"},
    }
    event = SimpleNamespace(
        event_data=CommandRegisterEventData(
            commands={"/sites": {"description": "管理站点"}, "/unknown": {"description": "无效"}},
            origin="DiscordFilter",
            service="discord-main",
        )
    )

    with (
        patch.object(
            module,
            "get_configs",
            return_value={"discord-main": SimpleNamespace(name="discord-main", config={})},
        ),
        patch.object(module, "get_instance", return_value=client),
        patch("app.modules.discord.eventmanager.send_event", return_value=event),
    ):
        module.register_commands(original_commands)

    client.register_commands.assert_called_once_with(
        {"/sites": {"description": "管理站点"}}
    )
    client.delete_commands.assert_not_called()


def test_discord_module_register_commands_deletes_when_event_canceled():
    """Discord 模块注册命令被事件取消时应清理应用命令。"""
    module = DiscordModule()
    client = SimpleNamespace(register_commands=Mock(), delete_commands=Mock())
    event = SimpleNamespace(
        event_data=CommandRegisterEventData(
            commands={"/sites": {"description": "管理站点"}},
            origin="DiscordFilter",
            service="discord-main",
            cancel=True,
        )
    )

    with (
        patch.object(
            module,
            "get_configs",
            return_value={"discord-main": SimpleNamespace(name="discord-main", config={})},
        ),
        patch.object(module, "get_instance", return_value=client),
        patch("app.modules.discord.eventmanager.send_event", return_value=event),
    ):
        module.register_commands({"/sites": {"description": "管理站点"}})

    client.delete_commands.assert_called_once_with()
    client.register_commands.assert_not_called()


def test_discord_normalizes_slash_command_names():
    """Discord 命令名称应符合平台只允许小写字母数字下划线连字符的约束。"""
    assert Discord._normalize_slash_command_name("/sites") == "sites"
    assert Discord._normalize_slash_command_name("/clear_cache") == "clear_cache"
    assert Discord._normalize_slash_command_name("/INVALID") == "invalid"
    assert Discord._normalize_slash_command_name("/中文") == ""
    assert Discord._normalize_slash_command_name("/" + "a" * 33) == ""


def test_discord_handle_slash_command_forwards_to_message_chain():
    """Discord 斜杠命令回调应转发为统一消息入口可识别的命令文本。"""
    client = Discord.__new__(Discord)
    client._update_user_chat_mapping = Mock()
    client._post_to_ds = AsyncMock()

    user = SimpleNamespace(id=10001, display_name="tester", global_name=None, name="tester")
    channel = SimpleNamespace(id=20001)
    interaction = SimpleNamespace(
        id=30001,
        user=user,
        channel=channel,
        response=SimpleNamespace(
            defer=AsyncMock(),
            is_done=Mock(return_value=True),
            send_message=AsyncMock(),
        ),
        followup=SimpleNamespace(send=AsyncMock()),
    )

    asyncio.run(client._handle_slash_command(interaction, "/sites", "refresh"))

    client._update_user_chat_mapping.assert_called_once_with("10001", "20001")
    client._post_to_ds.assert_awaited_once_with(
        {
            "type": "message",
            "userid": "10001",
            "username": "tester",
            "user_tag": str(user),
            "text": "/sites refresh",
            "message_id": "30001",
            "chat_id": "20001",
            "channel_type": "guild",
        }
    )
    interaction.followup.send.assert_awaited_once_with(
        "命令已提交，请稍等...",
        ephemeral=True,
    )
