"""Entry point discovery for pip-installed skills.

This module provides functionality to discover and load skills
that are installed as Python packages via entry points.
"""

from __future__ import annotations

import logging
from collections.abc import Iterator
from importlib.metadata import EntryPoint, entry_points

from oclawma.skills.base import SkillMetadata
from oclawma.skills.skill import LazySkill

logger = logging.getLogger(__name__)

# Entry point group for OCLAWMA skills
SKILL_ENTRY_POINT_GROUP = "oclawma.skills"


class EntryPointDiscovery:
    """Discover skills from Python package entry points.

    This class scans for skills that have been installed via pip
    and registered using the 'oclawma.skills' entry point group.

    Example:
        >>> discovery = EntryPointDiscovery()
        >>> for skill_class, metadata in discovery.discover():
        ...     print(f"Found skill: {metadata.name}")
    """

    def __init__(self) -> None:
        """Initialize the entry point discovery."""
        self._discovered: dict[str, tuple[type[LazySkill], SkillMetadata]] = {}

    def discover(self, name: str | None = None) -> Iterator[tuple[type[LazySkill], SkillMetadata]]:
        """Discover skills from entry points.

        Args:
            name: Optional specific skill name to discover.
                  If None, discovers all available skills.

        Yields:
            Tuples of (skill_class, metadata) for each discovered skill.
        """
        try:
            eps = entry_points()

            # Handle different Python versions' entry_points() behavior
            if hasattr(eps, "select"):
                # Python 3.10+ - use select()
                skill_eps = eps.select(group=SKILL_ENTRY_POINT_GROUP)
            else:
                # Python 3.9 - dictionary-style access
                skill_eps = eps.get(SKILL_ENTRY_POINT_GROUP, [])

            for ep in skill_eps:
                if name is not None and ep.name != name:
                    continue

                try:
                    skill_class, metadata = self._load_entry_point(ep)
                    if skill_class and metadata:
                        self._discovered[metadata.name] = (skill_class, metadata)
                        yield skill_class, metadata
                except Exception as e:
                    logger.warning(f"Failed to load skill entry point '{ep.name}': {e}")

        except Exception as e:
            logger.warning(f"Failed to discover skills from entry points: {e}")

    def discover_all(self) -> dict[str, tuple[type[LazySkill], SkillMetadata]]:
        """Discover all skills and return as a dictionary.

        Returns:
            Dictionary mapping skill names to (skill_class, metadata) tuples.
        """
        result = {}
        for skill_class, metadata in self.discover():
            result[metadata.name] = (skill_class, metadata)
        return result

    def get_skill(self, name: str) -> tuple[type[LazySkill], SkillMetadata] | None:
        """Get a specific skill by name.

        Args:
            name: Skill name (entry point name)

        Returns:
            Tuple of (skill_class, metadata) or None if not found.
        """
        if name in self._discovered:
            return self._discovered[name]

        try:
            eps = entry_points()

            if hasattr(eps, "select"):
                skill_eps = list(eps.select(group=SKILL_ENTRY_POINT_GROUP, name=name))
            else:
                skill_eps = [ep for ep in eps.get(SKILL_ENTRY_POINT_GROUP, []) if ep.name == name]

            if not skill_eps:
                return None

            return self._load_entry_point(skill_eps[0])

        except Exception as e:
            logger.warning(f"Failed to get skill '{name}': {e}")
            return None

    def _load_entry_point(
        self, ep: EntryPoint
    ) -> tuple[type[LazySkill], SkillMetadata] | tuple[None, None]:
        """Load a skill from an entry point.

        Args:
            ep: The entry point to load

        Returns:
            Tuple of (skill_class, metadata) or (None, None) on failure.
        """
        try:
            # Load the entry point
            obj = ep.load()

            # Check if it's a skill class
            if isinstance(obj, type) and issubclass(obj, LazySkill):
                # Create metadata from entry point info
                metadata = SkillMetadata(
                    name=ep.name,
                    version=self._get_version(ep),
                    description=obj.__doc__ or f"Skill: {ep.name}",
                    entry_point=f"{ep.module}:{obj.__name__}",
                )
                return obj, metadata

            # Check if it's a module with a get_skill function
            elif hasattr(obj, "get_skill"):
                skill_instance = obj.get_skill()
                if isinstance(skill_instance, LazySkill):
                    return type(skill_instance), skill_instance.metadata

            # Check if it's a callable that returns a skill
            elif callable(obj) and not isinstance(obj, type):
                try:
                    skill_instance = obj()
                    if isinstance(skill_instance, LazySkill):
                        return type(skill_instance), skill_instance.metadata
                except Exception:
                    pass

            logger.warning(f"Entry point '{ep.name}' does not provide a valid skill")
            return None, None

        except Exception as e:
            logger.warning(f"Failed to load entry point '{ep.name}': {e}")
            return None, None

    def _get_version(self, ep: EntryPoint) -> str:
        """Get the version of the package providing an entry point.

        Args:
            ep: The entry point

        Returns:
            Package version string or "unknown".
        """
        try:
            from importlib.metadata import version

            return version(ep.module.split(".")[0])
        except Exception:
            return "unknown"

    def list_available(self) -> list[str]:
        """List all available skill names from entry points.

        Returns:
            List of skill names.
        """
        try:
            eps = entry_points()

            if hasattr(eps, "select"):
                return [ep.name for ep in eps.select(group=SKILL_ENTRY_POINT_GROUP)]
            else:
                return [ep.name for ep in eps.get(SKILL_ENTRY_POINT_GROUP, [])]

        except Exception as e:
            logger.warning(f"Failed to list available skills: {e}")
            return []

    def clear_cache(self) -> None:
        """Clear the discovery cache."""
        self._discovered.clear()


def register_entry_point_skills(registry, names: list[str] | None = None) -> list[str]:
    """Register pip-installed skills with a SkillRegistry.

    This is a convenience function to discover and register all
    pip-installed skills with a registry.

    Args:
        registry: The SkillRegistry to register skills with
        names: Optional list of specific skill names to register.
               If None, registers all discovered skills.

    Returns:
        List of registered skill names.
    """
    discovery = EntryPointDiscovery()
    registered = []

    if names:
        for name in names:
            result = discovery.get_skill(name)
            if result:
                skill_class, metadata = result
                registry.register_skill_class(name, skill_class, metadata)
                registered.append(name)
    else:
        for skill_class, metadata in discovery.discover():
            registry.register_skill_class(metadata.name, skill_class, metadata)
            registered.append(metadata.name)

    return registered
