"""Tests for the plugin registry."""

from __future__ import annotations

from typing import Any

import pytest

from agent_scout.plugins.base import Plugin, PluginStatus, ToolSpec
from agent_scout.plugins.registry import PluginRegistry

# --- Test plugins ---


class AlphaPlugin(Plugin):
    @property
    def name(self) -> str:
        return "alpha"

    @property
    def description(self) -> str:
        return "Alpha plugin"

    def get_tools(self) -> list[ToolSpec]:
        return [ToolSpec(name="alpha_tool", description="Alpha does things")]

    async def execute(self, tool_name: str, tool_input: dict[str, Any]) -> str:
        return f"alpha: {tool_input}"


class BetaPlugin(Plugin):
    """Depends on AlphaPlugin."""

    @property
    def name(self) -> str:
        return "beta"

    @property
    def description(self) -> str:
        return "Beta plugin"

    @property
    def dependencies(self) -> list[str]:
        return ["alpha"]

    def get_tools(self) -> list[ToolSpec]:
        return [ToolSpec(name="beta_tool", description="Beta does things")]

    async def execute(self, tool_name: str, tool_input: dict[str, Any]) -> str:
        return f"beta: {tool_input}"


class GammaPlugin(Plugin):
    """Depends on BetaPlugin (transitive dep on Alpha)."""

    @property
    def name(self) -> str:
        return "gamma"

    @property
    def description(self) -> str:
        return "Gamma plugin"

    @property
    def dependencies(self) -> list[str]:
        return ["beta"]

    def get_tools(self) -> list[ToolSpec]:
        return []

    async def execute(self, tool_name: str, tool_input: dict[str, Any]) -> str:
        return ""


class CircularAPlugin(Plugin):
    @property
    def name(self) -> str:
        return "circ_a"

    @property
    def description(self) -> str:
        return "Circular A"

    @property
    def dependencies(self) -> list[str]:
        return ["circ_b"]

    def get_tools(self) -> list[ToolSpec]:
        return []

    async def execute(self, tool_name: str, tool_input: dict[str, Any]) -> str:
        return ""


class CircularBPlugin(Plugin):
    @property
    def name(self) -> str:
        return "circ_b"

    @property
    def description(self) -> str:
        return "Circular B"

    @property
    def dependencies(self) -> list[str]:
        return ["circ_a"]

    def get_tools(self) -> list[ToolSpec]:
        return []

    async def execute(self, tool_name: str, tool_input: dict[str, Any]) -> str:
        return ""


# --- Tests ---


async def test_register_and_get():
    reg = PluginRegistry()
    plugin = AlphaPlugin()
    reg.register(plugin)

    assert reg.get("alpha") is plugin
    assert reg.get("missing") is None


async def test_register_duplicate_raises():
    reg = PluginRegistry()
    reg.register(AlphaPlugin())
    with pytest.raises(ValueError, match="already registered"):
        reg.register(AlphaPlugin())


async def test_unregister():
    reg = PluginRegistry()
    reg.register(AlphaPlugin())
    reg.unregister("alpha")
    assert reg.get("alpha") is None


async def test_load_all():
    reg = PluginRegistry()
    reg.register(AlphaPlugin())
    reg.register(BetaPlugin())

    await reg.load_all()

    assert reg.get("alpha").status == PluginStatus.ENABLED
    assert reg.get("beta").status == PluginStatus.ENABLED


async def test_unload_all():
    reg = PluginRegistry()
    reg.register(AlphaPlugin())
    await reg.load_all()

    assert reg.get("alpha").status == PluginStatus.ENABLED

    await reg.unload_all()
    assert reg.get("alpha").status == PluginStatus.UNLOADED


async def test_dependency_order():
    reg = PluginRegistry()
    # Register in reverse dependency order
    reg.register(GammaPlugin())
    reg.register(BetaPlugin())
    reg.register(AlphaPlugin())

    order = reg._resolve_dependencies()
    # Alpha must come before Beta, Beta before Gamma
    assert order.index("alpha") < order.index("beta")
    assert order.index("beta") < order.index("gamma")


async def test_circular_dependency_detected():
    reg = PluginRegistry()
    reg.register(CircularAPlugin())
    reg.register(CircularBPlugin())

    with pytest.raises(RuntimeError, match="Circular dependency"):
        reg._resolve_dependencies()


async def test_get_enabled():
    reg = PluginRegistry()
    reg.register(AlphaPlugin())
    reg.register(BetaPlugin())

    # Before load — nothing enabled
    assert len(reg.get_enabled()) == 0

    await reg.load_all()
    assert len(reg.get_enabled()) == 2


async def test_execute_tool():
    reg = PluginRegistry()
    reg.register(AlphaPlugin())
    reg.register(BetaPlugin())
    await reg.load_all()

    result = await reg.execute("alpha_tool", {"key": "val"})
    assert "alpha" in result

    result = await reg.execute("beta_tool", {"key": "val"})
    assert "beta" in result


async def test_execute_unknown_tool():
    reg = PluginRegistry()
    reg.register(AlphaPlugin())
    await reg.load_all()

    with pytest.raises(ValueError, match="No enabled plugin"):
        await reg.execute("nonexistent_tool", {})


async def test_get_available_tools():
    reg = PluginRegistry()
    reg.register(AlphaPlugin())
    reg.register(BetaPlugin())
    await reg.load_all()

    tools = reg.get_available_tools()
    names = {t["name"] for t in tools}
    assert "alpha_tool" in names
    assert "beta_tool" in names


async def test_plugins_property():
    reg = PluginRegistry()
    reg.register(AlphaPlugin())
    plugins = reg.plugins
    assert "alpha" in plugins
    # Should be a copy
    plugins.pop("alpha")
    assert reg.get("alpha") is not None


async def test_load_with_failing_plugin():
    """A plugin that fails to load gets ERROR status, others still load."""

    class FailingPlugin(Plugin):
        @property
        def name(self) -> str:
            return "failing"

        @property
        def description(self) -> str:
            return "Fails on load"

        def get_tools(self) -> list[ToolSpec]:
            return []

        async def execute(self, tool_name: str, tool_input: dict[str, Any]) -> str:
            return ""

        async def on_load(self) -> None:
            raise RuntimeError("Boom")

    reg = PluginRegistry()
    reg.register(AlphaPlugin())
    reg.register(FailingPlugin())

    await reg.load_all()

    assert reg.get("alpha").status == PluginStatus.ENABLED
    assert reg.get("failing").status == PluginStatus.ERROR
