"""Plugin base class and manager."""
from __future__ import annotations

import logging
import importlib
from pathlib import Path
from typing import Any, Callable
from dataclasses import dataclass, field

log = logging.getLogger(__name__)


@dataclass
class Capability:
    """A single capability provided by a plugin."""
    name: str
    description: str
    handler: Callable
    plugin: str


def capability(description: str):
    """Decorator to mark a method as a plugin capability."""
    def decorator(func):
        func._capability = description
        return func
    return decorator


class Plugin:
    """Base class for all HiveMind plugins."""
    name: str = "unnamed"
    version: str = "0.1.0"
    description: str = ""

    def __init__(self, node: Any = None):
        self.node = node
        self._capabilities: list[Capability] = []
        self._discover_capabilities()

    def _discover_capabilities(self) -> None:
        """Find all methods decorated with @capability."""
        for attr_name in dir(self):
            attr = getattr(self, attr_name, None)
            if callable(attr) and hasattr(attr, "_capability"):
                self._capabilities.append(Capability(
                    name=attr_name,
                    description=attr._capability,
                    handler=attr,
                    plugin=self.name,
                ))

    @property
    def capabilities(self) -> list[Capability]:
        return self._capabilities

    async def initialize(self) -> None:
        """Called when plugin is loaded. Override for setup."""
        pass

    async def shutdown(self) -> None:
        """Called when plugin is unloaded. Override for cleanup."""
        pass


class PluginManager:
    """Discovers, loads and manages plugins."""

    # Built-in plugin modules — alle Plugins werden als Community-Plugins
    # über den Marketplace bereitgestellt. Diese Liste ist bewusst leer.
    BUILTIN: dict = {}

    # Human-readable metadata for built-in plugins
    BUILTIN_META: dict = {}

    def __init__(self, node: Any = None):
        self.node = node
        self._plugins: dict[str, Plugin] = {}

    async def load(self, plugin_names: list[str], plugin_dir: str = "./plugins") -> None:
        """Load plugins by name."""
        for name in plugin_names:
            try:
                plugin = self._load_one(name, plugin_dir)
                if plugin:
                    await plugin.initialize()
                    self._plugins[name] = plugin
                    caps = ", ".join(c.name for c in plugin.capabilities)
                    log.info("Plugin loaded: %s [%s]", name, caps)
            except Exception as e:
                log.error("Failed to load plugin '%s': %s", name, e)

    def _load_one(self, name: str, plugin_dir: str) -> Plugin | None:
        """Load a single plugin by name."""
        # Try built-in first
        if name in self.BUILTIN:
            mod = importlib.import_module(self.BUILTIN[name])
            cls = getattr(mod, "PLUGIN_CLASS")
            return cls(node=self.node)

        # Try community plugin directory
        plugin_path = Path(plugin_dir) / name / "__init__.py"
        if plugin_path.exists():
            import importlib.util as ilu
            spec = ilu.spec_from_file_location(f"plugins.{name}", plugin_path)
            mod = ilu.module_from_spec(spec)
            spec.loader.exec_module(mod)
            cls = getattr(mod, "PLUGIN_CLASS")
            return cls(node=self.node)

        log.warning("Plugin not found: %s", name)
        return None

    def get(self, name: str) -> Plugin | None:
        return self._plugins.get(name)

    def all_capabilities(self) -> list[Capability]:
        """Get all capabilities from all loaded plugins."""
        caps = []
        for plugin in self._plugins.values():
            caps.extend(plugin.capabilities)
        return caps

    def capabilities_prompt(self) -> str:
        """Generate a system prompt section describing available capabilities."""
        lines = ["Available tools:"]
        for cap in self.all_capabilities():
            lines.append(f"- {cap.plugin}.{cap.name}: {cap.description}")
        return "\n".join(lines)

    async def execute(self, plugin_name: str, capability_name: str, **kwargs) -> Any:
        """Execute a specific capability."""
        plugin = self._plugins.get(plugin_name)
        if not plugin:
            raise ValueError(f"Plugin not loaded: {plugin_name}")
        
        for cap in plugin.capabilities:
            if cap.name == capability_name:
                return await cap.handler(**kwargs)
        
        raise ValueError(f"Capability not found: {plugin_name}.{capability_name}")

    async def shutdown_all(self) -> None:
        for plugin in self._plugins.values():
            await plugin.shutdown()

    @property
    def loaded(self) -> list[str]:
        return list(self._plugins.keys())

    # ── Runtime management ───────────────────────────────────────

    def get_status(self, name: str, enabled_names: list) -> dict:
        """Return status dict for a plugin (builtin or community)."""
        meta = self.BUILTIN_META.get(name, {})
        loaded = self._plugins.get(name)
        return {
            "name": name,
            "description": meta.get("description", ""),
            "version": meta.get("version", "1.0.0"),
            "icon": meta.get("icon", "🔌"),
            "tags": meta.get("tags", []),
            "enabled": name in enabled_names,
            "loaded": loaded is not None,
            "capabilities": [c.name for c in loaded.capabilities] if loaded else [],
            "builtin": True,
        }

    def installed_community(self, plugin_dir: str) -> list:
        """List installed community plugins (subdirs of plugin_dir with __init__.py)."""
        pdir = Path(plugin_dir)
        if not pdir.exists():
            return []
        result = []
        for subdir in sorted(pdir.iterdir()):
            if not subdir.is_dir() or subdir.name.startswith("_"):
                continue
            if not (subdir / "__init__.py").exists():
                continue
            name = subdir.name
            loaded = self._plugins.get(name)
            # Try to read metadata from plugin class
            meta: dict = {"description": "", "version": "?", "icon": "🔌", "tags": []}
            try:
                src = (subdir / "__init__.py").read_text(encoding="utf-8")
                for attr in ("description", "version"):
                    import re as _re
                    m = _re.search(rf'{attr}\s*=\s*["\']([^"\']+)["\']', src)
                    if m:
                        meta[attr] = m.group(1)
            except Exception:
                pass
            result.append({
                "name": name,
                "description": meta["description"],
                "version": meta["version"],
                "icon": meta["icon"],
                "tags": meta["tags"],
                "enabled": loaded is not None,
                "capabilities": [c.name for c in loaded.capabilities] if loaded else [],
                "builtin": False,
            })
        return result

    async def enable_live(self, name: str, plugin_dir: str) -> bool:
        """Load a plugin at runtime without restarting HiveMind."""
        if name not in self._plugins:
            try:
                await self.load([name], plugin_dir)
                return True
            except Exception as e:
                log.error("Failed to enable plugin '%s' live: %s", name, e)
                return False
        return True

    async def disable_live(self, name: str) -> bool:
        """Unload a plugin at runtime without restarting HiveMind."""
        plugin = self._plugins.pop(name, None)
        if plugin:
            try:
                await plugin.shutdown()
            except Exception:
                pass
        return True

    async def install_from_url(self, slug: str, download_url: str, plugin_dir: str) -> tuple:
        """Download and install a community plugin zip from a URL."""
        import asyncio as _aio
        loop = _aio.get_event_loop()

        def _sync_install() -> tuple:
            import io, zipfile, shutil
            import urllib.request as _ur
            pdir = Path(plugin_dir)
            pdir.mkdir(parents=True, exist_ok=True)
            target = pdir / slug
            # Download
            try:
                req = _ur.Request(download_url, headers={"User-Agent": "HiveMind"})
                data = _ur.urlopen(req, timeout=30).read()
            except Exception as e:
                return False, f"Download fehlgeschlagen: {e}"
            # Extract
            try:
                with zipfile.ZipFile(io.BytesIO(data)) as zf:
                    for member in zf.namelist():
                        parts = Path(member).parts
                        if not parts:
                            continue
                        # Strip leading dir if it matches slug
                        if len(parts) > 1 and parts[0].rstrip("/") == slug:
                            out_path = target / Path(*parts[1:])
                        else:
                            out_path = target / Path(*parts)
                        # Security: no path traversal
                        if ".." in str(out_path):
                            continue
                        out_path.parent.mkdir(parents=True, exist_ok=True)
                        if not member.endswith("/"):
                            out_path.write_bytes(zf.read(member))
            except zipfile.BadZipFile as e:
                return False, f"Ungültige ZIP-Datei: {e}"
            except Exception as e:
                shutil.rmtree(target, ignore_errors=True)
                return False, f"Entpacken fehlgeschlagen: {e}"
            # Validate: must have __init__.py with PLUGIN_CLASS
            init = target / "__init__.py"
            if not init.exists():
                shutil.rmtree(target, ignore_errors=True)
                return False, "__init__.py nicht gefunden"
            try:
                src = init.read_text(encoding="utf-8")
                if "PLUGIN_CLASS" not in src:
                    shutil.rmtree(target, ignore_errors=True)
                    return False, "PLUGIN_CLASS nicht in __init__.py definiert"
            except Exception as e:
                return False, str(e)
            return True, ""

        return await loop.run_in_executor(None, _sync_install)

    def uninstall_community(self, name: str, plugin_dir: str) -> bool:
        """Delete a community plugin directory from disk."""
        import shutil
        pdir = Path(plugin_dir) / name
        if pdir.exists() and pdir.is_dir():
            shutil.rmtree(pdir, ignore_errors=True)
            return True
        return False
