"""
Doit Agent - Plugin System
Dynamic plugin discovery, sandboxing, and tool registration.
Plugins are Python files placed in the plugins directory.

Plugin API:
-----------
A plugin file must define:

  PLUGIN_NAME = "my_plugin"
  PLUGIN_VERSION = "1.0.0"
  PLUGIN_DOIT_MIN_VERSION = "1.0.0"
  PLUGIN_DESCRIPTION = "What this plugin does"

  # List of tool (name, description, async_function, is_dangerous)
  TOOLS = [
      ("my_tool", "Does something", my_tool_fn, False),
  ]

  async def my_tool_fn(arg1: str, arg2: int = 0, **kwargs) -> dict:
      # Tool must return a dict
      return {"result": "..."}
"""
from __future__ import annotations

import importlib.util
import logging
import sys
from pathlib import Path
from typing import Any, Callable, Optional

from core.config import PLUGIN_DIR, VERSION
from tools.registry import get_registry
from persistence.database import get_db

logger = logging.getLogger("doit.plugins")


def _version_compatible(min_version: str, current: str) -> bool:
    """Check if current version meets minimum requirement."""
    def parse(v):
        return tuple(int(x) for x in v.split(".")[:3])
    try:
        return parse(current) >= parse(min_version)
    except Exception:
        return False


class PluginManager:
    """Discovers and loads plugins from the plugin directory."""

    def __init__(self):
        self._loaded: dict[str, dict] = {}

    async def discover_and_load(self) -> list[str]:
        """Scan plugin directory and load all valid plugins."""
        PLUGIN_DIR.mkdir(parents=True, exist_ok=True)
        loaded = []

        for path in PLUGIN_DIR.glob("*.py"):
            if path.name.startswith("_"):
                continue
            try:
                name = await self._load_plugin(path)
                if name:
                    loaded.append(name)
            except Exception as e:
                logger.error("Failed to load plugin %s: %s", path.name, e)

        return loaded

    async def _load_plugin(self, path: Path) -> Optional[str]:
        """Load a single plugin file. Returns plugin name or None."""
        spec = importlib.util.spec_from_file_location(f"doit_plugin_{path.stem}", path)
        if not spec or not spec.loader:
            return None

        module = importlib.util.module_from_spec(spec)

        # Validate required attributes before executing
        # We do a text scan first to fail fast
        source = path.read_text()
        if "PLUGIN_NAME" not in source:
            logger.debug("Skipping %s: not a doit plugin", path.name)
            return None

        try:
            spec.loader.exec_module(module)
        except Exception as e:
            logger.error("Plugin %s exec error: %s", path.name, e)
            return None

        # Validate attributes
        required = ["PLUGIN_NAME", "PLUGIN_VERSION", "PLUGIN_DOIT_MIN_VERSION", "TOOLS"]
        for attr in required:
            if not hasattr(module, attr):
                logger.error("Plugin %s missing attribute: %s", path.name, attr)
                return None

        plugin_name = module.PLUGIN_NAME
        min_ver = module.PLUGIN_DOIT_MIN_VERSION

        if not _version_compatible(min_ver, VERSION):
            logger.error(
                "Plugin %s requires doit >= %s (current: %s)",
                plugin_name, min_ver, VERSION
            )
            return None

        # Register tools
        registry = get_registry()
        tool_names = []
        for tool_def in module.TOOLS:
            t_name, t_desc, t_fn, t_dangerous = tool_def
            # Sandbox: wrap in try/except to isolate plugin errors
            sandboxed = _make_sandboxed(t_name, t_fn)
            registry.register(t_name, t_desc, sandboxed, t_dangerous)
            tool_names.append(t_name)
            logger.info("Plugin %s registered tool: %s", plugin_name, t_name)

        self._loaded[plugin_name] = {
            "name": plugin_name,
            "version": module.PLUGIN_VERSION,
            "path": str(path),
            "tools": tool_names,
            "description": getattr(module, "PLUGIN_DESCRIPTION", ""),
        }

        # Persist to DB
        db = await get_db()
        await db.plugin_register({
            "name": plugin_name,
            "version": module.PLUGIN_VERSION,
            "path": str(path),
            "tools": tool_names,
            "enabled": True,
        })

        logger.info("Loaded plugin: %s v%s (%d tools)", plugin_name, module.PLUGIN_VERSION, len(tool_names))
        return plugin_name

    def loaded_plugins(self) -> list[dict]:
        return list(self._loaded.values())


def _make_sandboxed(tool_name: str, fn: Callable) -> Callable:
    """Wrap tool function in a sandbox to isolate plugin errors."""
    async def sandboxed(**kwargs) -> dict:
        try:
            result = await fn(**kwargs)
            if not isinstance(result, dict):
                result = {"result": str(result)}
            return result
        except Exception as e:
            logger.error("Plugin tool %s error: %s", tool_name, e)
            return {"error": f"Plugin tool error: {e}"}
    sandboxed.__name__ = tool_name
    return sandboxed


# ── Example Plugin Template (written to disk on first run) ─────────────────

EXAMPLE_PLUGIN = '''"""
Example Doit Plugin - Custom Greeting Tool
Place this file in ~/.config/doit/plugins/ to activate.
"""

PLUGIN_NAME = "example_greeter"
PLUGIN_VERSION = "1.0.0"
PLUGIN_DOIT_MIN_VERSION = "1.0.0"
PLUGIN_DESCRIPTION = "Adds a custom greeting tool"


async def greet(name: str = "World", **kwargs) -> dict:
    """Say hello to someone."""
    return {"greeting": f"Hello, {name}! I am Doit.", "success": True}


TOOLS = [
    ("greet", "Say hello to someone", greet, False),
]
'''


async def write_example_plugin():
    """Write the example plugin to the plugin directory."""
    PLUGIN_DIR.mkdir(parents=True, exist_ok=True)
    example = PLUGIN_DIR / "example_greeter.py.example"
    if not example.exists():
        example.write_text(EXAMPLE_PLUGIN)


# Singleton
_manager: Optional[PluginManager] = None


def get_plugin_manager() -> PluginManager:
    global _manager
    if _manager is None:
        _manager = PluginManager()
    return _manager
