"""Plugin registry and discovery.

Plugins can extend Impakt with custom readers, transforms, injury criteria,
protocol scorers, and report templates.
"""

from __future__ import annotations

import importlib
import logging
import sys
from pathlib import Path
from typing import Any, Protocol, runtime_checkable

logger = logging.getLogger(__name__)


@runtime_checkable
class ImpaktPlugin(Protocol):
    """Protocol for Impakt plugins."""

    @property
    def name(self) -> str: ...

    @property
    def version(self) -> str: ...

    def register(self, registry: PluginRegistry) -> None:
        """Register plugin components with the registry."""
        ...


class PluginRegistry:
    """Central registry for all plugin-provided extensions."""

    def __init__(self) -> None:
        self._readers: list[Any] = []
        self._transforms: list[Any] = []
        self._criteria: list[Any] = []
        self._protocols: list[Any] = []
        self._report_templates: list[Any] = []
        self._plugins: list[ImpaktPlugin] = []

    def register_reader(self, reader: Any) -> None:
        """Register a data reader."""
        self._readers.append(reader)
        logger.info("Plugin reader registered: %s", getattr(reader, "format_name", reader))

    def register_transform(self, name: str, transform_cls: type) -> None:
        """Register a transform type."""
        self._transforms.append((name, transform_cls))
        # Also register with the transform registry
        from impakt.transform.base import _registry

        _registry.register(name, transform_cls)
        logger.info("Plugin transform registered: %s", name)

    def register_criterion(self, criterion: Any) -> None:
        """Register an injury criterion."""
        self._criteria.append(criterion)
        logger.info("Plugin criterion registered: %s", getattr(criterion, "name", criterion))

    def register_protocol(self, protocol: Any) -> None:
        """Register a rating protocol scorer."""
        self._protocols.append(protocol)
        logger.info("Plugin protocol registered: %s", getattr(protocol, "protocol_name", protocol))

    def register_report_template(self, name: str, template: Any) -> None:
        """Register a report template."""
        self._report_templates.append((name, template))
        logger.info("Plugin report template registered: %s", name)

    @property
    def readers(self) -> list[Any]:
        return list(self._readers)

    @property
    def transforms(self) -> list[tuple[str, type]]:
        return list(self._transforms)

    @property
    def criteria(self) -> list[Any]:
        return list(self._criteria)

    @property
    def protocols(self) -> list[Any]:
        return list(self._protocols)

    @property
    def report_templates(self) -> list[tuple[str, Any]]:
        return list(self._report_templates)

    @property
    def plugins(self) -> list[ImpaktPlugin]:
        return list(self._plugins)

    def register_plugin(self, plugin: ImpaktPlugin) -> None:
        """Register a complete plugin."""
        self._plugins.append(plugin)
        plugin.register(self)
        logger.info("Plugin registered: %s v%s", plugin.name, plugin.version)


# Global registry
_global_plugin_registry = PluginRegistry()


def get_plugin_registry() -> PluginRegistry:
    """Get the global plugin registry."""
    return _global_plugin_registry


def discover_entry_points() -> None:
    """Discover plugins via Python entry points.

    Looks for entry points in the ``impakt.plugins`` group.
    """
    if sys.version_info >= (3, 12):
        from importlib.metadata import entry_points

        eps = entry_points(group="impakt.plugins")
    else:
        from importlib.metadata import entry_points

        all_eps = entry_points()
        eps = all_eps.get("impakt.plugins", [])  # type: ignore[assignment]

    for ep in eps:
        try:
            plugin_cls = ep.load()
            plugin = plugin_cls()
            _global_plugin_registry.register_plugin(plugin)
        except Exception as e:
            logger.warning("Failed to load plugin %s: %s", ep.name, e)


def discover_directory(path: Path | None = None) -> None:
    """Discover plugins from a directory.

    Looks for Python files in ``~/.impakt/plugins/`` (default)
    that define an ``ImpaktPlugin`` class.
    """
    if path is None:
        path = Path.home() / ".impakt" / "plugins"

    if not path.exists():
        return

    for py_file in sorted(path.glob("*.py")):
        try:
            spec = importlib.util.spec_from_file_location(f"impakt_plugin_{py_file.stem}", py_file)
            if spec and spec.loader:
                module = importlib.util.module_from_spec(spec)
                spec.loader.exec_module(module)

                # Look for ImpaktPlugin implementations
                for attr_name in dir(module):
                    attr = getattr(module, attr_name)
                    if (
                        isinstance(attr, type)
                        and attr is not ImpaktPlugin
                        and hasattr(attr, "register")
                        and hasattr(attr, "name")
                        and hasattr(attr, "version")
                    ):
                        plugin = attr()
                        _global_plugin_registry.register_plugin(plugin)
        except Exception as e:
            logger.warning("Failed to load plugin from %s: %s", py_file, e)


def discover_all() -> None:
    """Run all plugin discovery mechanisms."""
    discover_entry_points()
    discover_directory()
