from __future__ import annotations

import importlib
import logging
import pkgutil
from typing import Any, Callable, Generic, TypeVar

T = TypeVar("T")
logger = logging.getLogger(__name__)


class Registry(Generic[T]):
    """Generic plugin registry with decorator-based registration and auto-discovery."""

    def __init__(self, name: str) -> None:
        self.name = name
        self._items: dict[str, type[T]] = {}
        self._discovered = False

    def register(self, key: str) -> Callable[[type[T]], type[T]]:
        """Decorator to register a class under a given key."""

        def decorator(cls: type[T]) -> type[T]:
            if key in self._items:
                logger.warning(
                    "Registry '%s': overwriting key '%s' (%s -> %s)",
                    self.name,
                    key,
                    self._items[key].__name__,
                    cls.__name__,
                )
            self._items[key] = cls
            return cls

        return decorator

    def get(self, key: str) -> type[T]:
        """Retrieve a registered class by key."""
        self._ensure_discovered()
        if key not in self._items:
            available = ", ".join(sorted(self._items.keys()))
            raise KeyError(
                f"Registry '{self.name}': key '{key}' not found. Available: [{available}]"
            )
        return self._items[key]

    def create(self, key: str, **kwargs: Any) -> T:
        """Instantiate a registered class by key with keyword arguments."""
        cls = self.get(key)
        return cls(**kwargs)  # type: ignore[call-arg]

    def keys(self) -> list[str]:
        """Return all registered keys."""
        self._ensure_discovered()
        return sorted(self._items.keys())

    def items(self) -> list[tuple[str, type[T]]]:
        """Return all registered (key, class) pairs."""
        self._ensure_discovered()
        return sorted(self._items.items(), key=lambda x: x[0])

    def _ensure_discovered(self) -> None:
        """Run auto-discovery once to import all plugin modules."""
        if not self._discovered:
            self._discovered = True
            self._discover()

    def _discover(self) -> None:
        """Import all submodules within known plugin packages to trigger registration."""
        package_map = {
            "attacks": "leshy.attacks",
            "targets": "leshy.targets",
            "evaluators": "leshy.evaluators",
            "loggers": "leshy.loggers",
            "buffs": "leshy.buffs",
        }
        pkg_name = package_map.get(self.name)
        if pkg_name is None:
            return
        try:
            pkg = importlib.import_module(pkg_name)
        except ImportError:
            logger.debug("Could not import package %s for auto-discovery", pkg_name)
            return
        if not hasattr(pkg, "__path__"):
            return
        for _importer, modname, _ispkg in pkgutil.walk_packages(
            pkg.__path__, prefix=pkg.__name__ + "."
        ):
            try:
                importlib.import_module(modname)
            except ImportError as e:
                logger.debug("Could not import %s during auto-discovery: %s", modname, e)


attack_registry: Registry[Any] = Registry("attacks")
target_registry: Registry[Any] = Registry("targets")
evaluator_registry: Registry[Any] = Registry("evaluators")
logger_registry: Registry[Any] = Registry("loggers")
buff_registry: Registry[Any] = Registry("buffs")
