import sys
from pathlib import Path
from dataclasses import asdict
from typing import TYPE_CHECKING, Set

import pytest
from nonebug import App

if TYPE_CHECKING:
    from nonebot.plugin import Plugin


@pytest.mark.asyncio
async def test_load_plugin(app: App):
    import nonebot

    # check regular
    assert nonebot.load_plugin("plugins.metadata")

    # check path
    assert nonebot.load_plugin(Path("plugins/export"))

    # check not found
    assert nonebot.load_plugin("some_plugin_not_exist") is None


@pytest.mark.asyncio
async def test_load_plugins(app: App, load_plugin: Set["Plugin"]):
    import nonebot
    from nonebot.plugin import PluginManager

    loaded_plugins = {
        plugin for plugin in nonebot.get_loaded_plugins() if not plugin.parent_plugin
    }
    assert loaded_plugins == load_plugin

    # check simple plugin
    assert "plugins.export" in sys.modules

    # check sub plugin
    plugin = nonebot.get_plugin("nested_subplugin")
    assert plugin
    assert "plugins.nested.plugins.nested_subplugin" in sys.modules
    assert plugin.parent_plugin == nonebot.get_plugin("nested")

    # check load again
    with pytest.raises(RuntimeError):
        PluginManager(plugins=["plugins.export"]).load_all_plugins()
    with pytest.raises(RuntimeError):
        PluginManager(search_path=["plugins"]).load_all_plugins()


@pytest.mark.asyncio
async def test_load_nested_plugin(app: App, load_plugin: Set["Plugin"]):
    import nonebot

    parent_plugin = nonebot.get_plugin("nested")
    sub_plugin = nonebot.get_plugin("nested_subplugin")
    sub_plugin2 = nonebot.get_plugin("nested_subplugin2")
    assert parent_plugin and sub_plugin and sub_plugin2
    assert sub_plugin.parent_plugin is parent_plugin
    assert sub_plugin2.parent_plugin is parent_plugin
    assert parent_plugin.sub_plugins == {sub_plugin, sub_plugin2}


@pytest.mark.asyncio
async def test_load_json(app: App):
    import nonebot

    nonebot.load_from_json("./plugins.json")

    with pytest.raises(TypeError):
        nonebot.load_from_json("./plugins.invalid.json")


@pytest.mark.asyncio
async def test_load_toml(app: App):
    import nonebot

    nonebot.load_from_toml("./plugins.toml")

    with pytest.raises(ValueError):
        nonebot.load_from_toml("./plugins.empty.toml")

    with pytest.raises(TypeError):
        nonebot.load_from_toml("./plugins.invalid.toml")


@pytest.mark.asyncio
async def test_bad_plugin(app: App):
    import nonebot

    nonebot.load_plugins("bad_plugins")

    assert nonebot.get_plugin("bad_plugins") is None


@pytest.mark.asyncio
async def test_require_loaded(app: App, monkeypatch: pytest.MonkeyPatch):
    import nonebot

    def _patched_find(name: str):
        assert False

    monkeypatch.setattr("nonebot.plugin.load._find_manager_by_name", _patched_find)

    nonebot.load_plugin("plugins.export")

    nonebot.require("plugins.export")


@pytest.mark.asyncio
async def test_require_not_loaded(app: App, monkeypatch: pytest.MonkeyPatch):
    import nonebot
    from nonebot.plugin import PluginManager, _managers

    m = PluginManager(["plugins.export"])
    _managers.append(m)

    origin_load = PluginManager.load_plugin

    def _patched_load(self: PluginManager, name: str):
        assert self is m
        return origin_load(self, name)

    monkeypatch.setattr(PluginManager, "load_plugin", _patched_load)

    nonebot.require("plugins.export")

    assert len(_managers) == 1


@pytest.mark.asyncio
async def test_require_not_declared(app: App):
    import nonebot
    from nonebot.plugin import _managers

    nonebot.require("plugins.export")

    assert len(_managers) == 1
    assert _managers[-1].plugins == {"plugins.export"}


@pytest.mark.asyncio
async def test_require_not_found(app: App):
    import nonebot

    with pytest.raises(RuntimeError):
        nonebot.require("some_plugin_not_exist")


@pytest.mark.asyncio
async def test_plugin_metadata(app: App, load_plugin: Set["Plugin"]):
    import nonebot
    from plugins.metadata import Config

    plugin = nonebot.get_plugin("metadata")
    assert plugin
    assert plugin.metadata
    assert asdict(plugin.metadata) == {
        "name": "测试插件",
        "description": "测试插件元信息",
        "usage": "无法使用",
        "config": Config,
        "extra": {"author": "NoneBot"},
    }