"""Base class for all AgentScout plugins."""

from __future__ import annotations

from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from enum import StrEnum
from typing import Any


class PluginStatus(StrEnum):
    """Plugin lifecycle status."""

    UNLOADED = "unloaded"
    LOADED = "loaded"
    ENABLED = "enabled"
    DISABLED = "disabled"
    ERROR = "error"


@dataclass
class PluginParam:
    """Describes a single parameter a plugin tool accepts."""

    name: str
    description: str
    param_type: str = "string"  # string, number, boolean, array, object
    required: bool = False
    default: Any = None


@dataclass
class ToolSpec:
    """Metadata about a tool provided by a plugin, for the LLM."""

    name: str
    description: str
    parameters: list[PluginParam] = field(default_factory=list)

    def to_dict(self) -> dict[str, Any]:
        """Convert to the dict format expected by the LLM/ReAct loop."""
        params = {}
        for p in self.parameters:
            params[p.name] = {
                "type": p.param_type,
                "description": p.description,
            }
            if p.default is not None:
                params[p.name]["default"] = p.default

        required = [p.name for p in self.parameters if p.required]

        return {
            "name": self.name,
            "description": self.description,
            "parameters": {
                "type": "object",
                "properties": params,
                "required": required,
            },
        }


@dataclass
class PluginConfig:
    """Configuration for a plugin instance."""

    enabled: bool = True
    settings: dict[str, Any] = field(default_factory=dict)


class Plugin(ABC):
    """Abstract base class for all AgentScout plugins.

    Each plugin provides one or more tools that the agent can use.
    Plugins have a lifecycle (load → enable → execute → disable → unload)
    and expose metadata so the LLM knows what tools are available.
    """

    def __init__(self, config: PluginConfig | None = None) -> None:
        self._config = config or PluginConfig()
        self._status = PluginStatus.UNLOADED

    @property
    @abstractmethod
    def name(self) -> str:
        """Unique plugin identifier."""
        ...

    @property
    @abstractmethod
    def description(self) -> str:
        """Human-readable description of what this plugin does."""
        ...

    @property
    def version(self) -> str:
        """Plugin version string."""
        return "0.1.0"

    @property
    def dependencies(self) -> list[str]:
        """List of plugin names this plugin depends on."""
        return []

    @property
    def status(self) -> PluginStatus:
        return self._status

    @property
    def config(self) -> PluginConfig:
        return self._config

    @property
    def enabled(self) -> bool:
        return self._config.enabled and self._status == PluginStatus.ENABLED

    @abstractmethod
    def get_tools(self) -> list[ToolSpec]:
        """Return the list of tools this plugin provides."""
        ...

    @abstractmethod
    async def execute(self, tool_name: str, tool_input: dict[str, Any]) -> str:
        """Execute a tool call and return the result as a string."""
        ...

    async def health_check(self) -> bool:
        """Check if the plugin is healthy and ready to execute.

        Override in subclasses that depend on external services.
        """
        return self.enabled

    async def on_load(self) -> None:  # noqa: B027
        """Called when the plugin is loaded. Override for setup logic."""

    async def on_unload(self) -> None:  # noqa: B027
        """Called when the plugin is unloaded. Override for cleanup logic."""

    async def on_config_change(self, new_config: PluginConfig) -> None:
        """Called when the plugin config changes at runtime."""
        self._config = new_config

    async def load(self) -> None:
        """Load and initialize the plugin."""
        await self.on_load()
        self._status = PluginStatus.LOADED

    async def enable(self) -> None:
        """Enable the plugin for use."""
        if self._status not in (PluginStatus.LOADED, PluginStatus.DISABLED):
            raise RuntimeError(f"Cannot enable plugin in state: {self._status}")
        self._config.enabled = True
        self._status = PluginStatus.ENABLED

    async def disable(self) -> None:
        """Disable the plugin without unloading."""
        self._config.enabled = False
        self._status = PluginStatus.DISABLED

    async def unload(self) -> None:
        """Unload and clean up the plugin."""
        await self.on_unload()
        self._status = PluginStatus.UNLOADED

    def get_tool_metadata(self) -> list[dict[str, Any]]:
        """Return tool metadata in the format expected by the ReAct loop."""
        return [tool.to_dict() for tool in self.get_tools()]
